diff --git a/.github/workflows/check_profiles.yml b/.github/workflows/check_profiles.yml
index 59c92e3ec0..db3ae6c4e8 100644
--- a/.github/workflows/check_profiles.yml
+++ b/.github/workflows/check_profiles.yml
@@ -1,8 +1,12 @@
name: Check profiles
on:
pull_request:
+ # release/* is included because pr-merge-bot.yml lets delegates merge into
+ # it, and it gates on this workflow's result. Without it a delegated merge
+ # into a release branch would run no profile validation at all.
branches:
- main
+ - release/*
paths:
- 'resources/profiles/**'
- ".github/workflows/check_profiles.yml"
@@ -20,6 +24,8 @@ permissions:
jobs:
check_profiles:
+ # This job name is the check-run name pr-merge-bot.yml requires before a
+ # delegated merge. Renaming it silently disables that gate.
name: Check profiles
runs-on: ubuntu-24.04
steps:
diff --git a/.github/workflows/pr-merge-bot.yml b/.github/workflows/pr-merge-bot.yml
new file mode 100644
index 0000000000..9b5ff2dfaf
--- /dev/null
+++ b/.github/workflows/pr-merge-bot.yml
@@ -0,0 +1,510 @@
+name: PR Merge Bot
+
+# Merges a pull request on request from a delegated vendor profile maintainer.
+# The merge is performed by this workflow's GITHUB_TOKEN, so a delegate needs no
+# repository access.
+#
+# Commands, posted as a comment on the PR:
+# /bot merge squash-merge the PR
+# /bot merge --dry-run report the verdict without merging
+#
+# Merges only when the commenter holds a grant covering every changed path, the
+# PR targets main or release/*, and CI is green on the head commit. Otherwise it
+# comments naming the files that fell outside the grant.
+#
+# Grants come from the FOLDER_MERGERS variable in the `merge-delegation`
+# environment: one per line, `account: path`, `#` comments and blank lines
+# allowed. Paths may contain spaces. A vendor takes two grants, the folder and
+# its sibling bundle JSON:
+#
+# # Acme profiles
+# vendor-maintainer: resources/profiles/Acme/
+# vendor-maintainer: resources/profiles/Acme.json
+#
+# Edit the grant list (environment scope, so admin only):
+# gh variable set FOLDER_MERGERS --env merge-delegation --body "$(cat folder-mergers.txt)"
+# gh variable get FOLDER_MERGERS --env merge-delegation
+#
+# Stop all merging without touching this file:
+# gh variable set MERGE_BOT_DRY_RUN --body true
+
+on:
+ issue_comment:
+ types:
+ - created
+
+# One merge attempt per PR at a time, so two quick comments cannot race.
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.issue.number }}
+ cancel-in-progress: false
+
+jobs:
+ merge:
+ # Skips the job unless a PR comment mentions the command.
+ if: >-
+ github.repository == 'OrcaSlicer/OrcaSlicer'
+ && github.event.issue.pull_request != null
+ && contains(github.event.comment.body, '/bot merge')
+ permissions:
+ contents: write # pulls.merge
+ pull-requests: write # pulls.merge
+ issues: write # feedback comment + reactions
+ actions: write # re-dispatch build_all.yml after the merge
+ runs-on: ubuntu-latest
+ timeout-minutes: 10
+ # Supplies FOLDER_MERGERS. Must carry no protection rules, or every
+ # delegated merge would wait for a human reviewer.
+ environment: merge-delegation
+ steps:
+ - name: Merge PR on behalf of a folder delegate
+ uses: actions/github-script@v9
+ env:
+ # Read as env vars, never interpolated into the script body.
+ FOLDER_MERGERS: ${{ vars.FOLDER_MERGERS }}
+ MERGE_BOT_DRY_RUN: ${{ vars.MERGE_BOT_DRY_RUN }}
+ with:
+ script: |
+ function isPermissionDenied(error) {
+ return error && error.status === 403 && /Resource not accessible by integration/i.test(error.message || '');
+ }
+
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
+
+ const MARKER = '';
+ // No grant may reach outside this root.
+ const DELEGATABLE_ROOT = 'resources/profiles/';
+ const ALLOWED_BASE_BRANCH = /^(?:main|release\/.+)$/;
+ const MERGE_METHOD = 'squash';
+ const REQUIRED_CHECK = 'Check profiles'; // job name in check_profiles.yml
+ const MAX_CHANGED_FILES = 500; // policy cap, well under listFiles' 3000
+ const LISTFILES_CAP = 3000;
+ const MAX_REPORTED_FILES = 12;
+ const MERGEABLE_ATTEMPTS = 5;
+ const MERGEABLE_DELAY_MS = 2000;
+ const OK_CONCLUSIONS = new Set(['success', 'neutral', 'skipped']);
+ const REGULAR_FILE_MODES = new Set(['100644', '100755']);
+
+ // Paths refused whatever the grants say. Checked before grants, so
+ // delegating a new root means removing it from this list too.
+ const DENIED_PATTERNS = [
+ /^\.github\//,
+ /(^|\/)\.git(attributes|modules|ignore|config)$/,
+ /^(?:src|deps|deps_src|tests|tools|cmake|sandboxes|scripts|docs?|localization|bbl)\//,
+ /(^|\/)cmakelists\.txt$/,
+ /\.cmake$/,
+ /^build_[^/]*\.(?:sh|bat)$/,
+ /^version\.inc$/,
+ // Executables, including those inside the delegatable root.
+ /\.(?:sh|bash|bat|cmd|ps1|py|js|mjs|cjs|ts|rb|pl|php)$/
+ ];
+
+ function parseGrants(raw) {
+ // GitHub login: 1-39 chars, alphanumerics with single interior hyphens.
+ const loginPattern = /^[A-Za-z0-9](?:[A-Za-z0-9]|-(?=[A-Za-z0-9])){0,38}$/;
+ const grantsByLogin = new Map();
+ const problems = [];
+
+ (raw || '').split(/\r?\n/).forEach((rawLine, index) => {
+ const line = rawLine.trim();
+ if (!line || line.startsWith('#')) {
+ return;
+ }
+
+ // Splits on the first colon only, so paths may contain ':' and spaces.
+ const separator = line.indexOf(':');
+ if (separator === -1) {
+ problems.push(`line ${index + 1}: expected \`account: path\``);
+ return;
+ }
+
+ const login = line.slice(0, separator).trim().replace(/^@/, '');
+ const path = line.slice(separator + 1).trim().replace(/\/+$/, '');
+
+ if (!loginPattern.test(login)) {
+ problems.push(`line ${index + 1}: \`${login}\` is not a valid GitHub account name`);
+ return;
+ }
+ if (/[\\*?\u0000-\u001f\u007f]/.test(path) || path.split('/').includes('..') || path.includes('//')) {
+ problems.push(`line ${index + 1}: invalid path (no globs, \`..\`, \`//\`, backslashes or control characters)`);
+ return;
+ }
+ // Rejects anything outside the root, and the bare root itself.
+ if (!path.startsWith(DELEGATABLE_ROOT) || path.length <= DELEGATABLE_ROOT.length) {
+ problems.push(`line ${index + 1}: \`${path}\` is not inside \`${DELEGATABLE_ROOT}\``);
+ return;
+ }
+
+ const key = login.toLowerCase();
+ grantsByLogin.set(key, (grantsByLogin.get(key) || []).concat(path));
+ });
+
+ return { grantsByLogin, problems };
+ }
+
+ function isDenied(path) {
+ if (/[\\\u0000-\u001f\u007f]/.test(path) || path.startsWith('/') || path.split('/').includes('..')) {
+ return true;
+ }
+
+ const normalized = path.normalize('NFKC').toLowerCase();
+ return DENIED_PATTERNS.some((pattern) => pattern.test(normalized));
+ }
+
+ // Byte-exact match on directory boundaries, so a grant of
+ // `.../Acme` covers neither `.../Acme Labs/x.json` nor `.../Acme.json`.
+ function isGranted(path, grants) {
+ return grants.some((grant) => path === grant || path.startsWith(`${grant}/`));
+ }
+
+ // Both endpoints of a rename; both must satisfy the grant.
+ function pathsFor(file) {
+ return [file.filename, file.previous_filename].filter(Boolean);
+ }
+
+ function formatList(items) {
+ const unique = [...new Set(items)];
+ const shown = unique.slice(0, MAX_REPORTED_FILES).map((item) => `- \`${item}\``);
+ if (unique.length > MAX_REPORTED_FILES) {
+ shown.push(`- …and ${unique.length - MAX_REPORTED_FILES} more`);
+ }
+ return shown.join('\n');
+ }
+
+ const { owner, repo } = context.repo;
+ const issue = context.payload.issue;
+ const comment = context.payload.comment;
+
+ if (!issue.pull_request) {
+ core.info('Ignoring comment that is not on a pull request.');
+ return;
+ }
+ // Ignores a comment whose sender is not its author.
+ if (context.payload.action !== 'created' || context.payload.sender.login !== comment.user.login) {
+ core.warning('Ignoring comment whose sender does not match its author.');
+ return;
+ }
+ if (comment.user.type !== 'User') {
+ core.info('Ignoring bot-authored command.');
+ return;
+ }
+
+ const commandLine = (comment.body || '')
+ .split('\n')
+ .map((line) => line.trim())
+ .find((line) => /^\/bot\s+merge\b/i.test(line));
+
+ if (!commandLine) {
+ core.info('No /bot merge command found.');
+ return;
+ }
+
+ const commenter = comment.user.login;
+ const { grantsByLogin, problems } = parseGrants(process.env.FOLDER_MERGERS);
+ const grants = grantsByLogin.get(commenter.toLowerCase()) || [];
+
+ for (const problem of problems) {
+ core.warning(`FOLDER_MERGERS ${problem}`);
+ }
+
+ // Says nothing to accounts with no grant, so it cannot be used to spam.
+ if (!grants.length) {
+ core.info(`Ignoring /bot merge from @${commenter}: not listed in FOLDER_MERGERS.`);
+ return;
+ }
+
+ // Warns instead of failing when the token cannot post feedback.
+ async function bestEffort(call, warning) {
+ try {
+ await call();
+ } catch (error) {
+ if (isPermissionDenied(error)) {
+ core.warning(warning);
+ return;
+ }
+
+ throw error;
+ }
+ }
+
+ const react = (content) => bestEffort(
+ () => github.rest.reactions.createForIssueComment({ owner, repo, comment_id: comment.id, content }),
+ `Cannot add the "${content}" reaction because the token cannot write.`);
+
+ const say = (body) => bestEffort(
+ () => github.rest.issues.createComment({ owner, repo, issue_number: issue.number, body: `${MARKER}\n${body}` }),
+ 'Cannot post a comment because the token cannot write comments.');
+
+ // Declines the command: warns in the log, reacts, explains on the PR.
+ async function refuse(reason) {
+ const configNote = problems.length
+ ? `\n\n\`FOLDER_MERGERS\` also has problems a maintainer needs to fix:\n${problems.map((problem) => `- ${problem}`).join('\n')}`
+ : '';
+ const grantsNote = `\n\nYour current grants
\n\n${formatList(grants)}\n\n `;
+
+ core.warning(`Refused /bot merge from @${commenter}: ${reason}`);
+ await react('-1');
+ await say(`@${commenter} I can't merge this PR: ${reason}${configNote}${grantsNote}`);
+ }
+
+ await react('eyes');
+
+ const args = (commandLine.match(/^\/bot\s+merge\s*(.*)$/i)[1] || '').trim().split(/\s+/).filter(Boolean);
+ const unknownArgs = args.filter((arg) => arg.toLowerCase() !== '--dry-run');
+ const dryRun = String(process.env.MERGE_BOT_DRY_RUN || '').toLowerCase() === 'true'
+ || unknownArgs.length !== args.length;
+
+ if (unknownArgs.length) {
+ return refuse(
+ `I don't understand ${unknownArgs.map((arg) => `\`${arg}\``).join(', ')}. ` +
+ 'Usage: `/bot merge` or `/bot merge --dry-run`.'
+ );
+ }
+
+ // Refuses everything while the grant list is malformed.
+ if (problems.length) {
+ return refuse(
+ 'the `FOLDER_MERGERS` grant list has malformed lines, so I refuse every merge until it is fixed.'
+ );
+ }
+
+ let { data: pr } = await github.rest.pulls.get({
+ owner,
+ repo,
+ pull_number: issue.number
+ });
+
+ if (pr.merged) {
+ return refuse('it is already merged.');
+ }
+ if (pr.state !== 'open') {
+ return refuse(`its state is \`${pr.state}\`, not \`open\`.`);
+ }
+ if (pr.draft) {
+ return refuse('it is still a draft. Mark it ready for review first.');
+ }
+ if (!ALLOWED_BASE_BRANCH.test(pr.base.ref)) {
+ return refuse(`it targets \`${pr.base.ref}\`. Delegated merges are only allowed into \`main\` and \`release/*\`.`);
+ }
+
+ // ---- folder scope ----
+ const files = await github.paginate(github.rest.pulls.listFiles, {
+ owner,
+ repo,
+ pull_number: pr.number,
+ per_page: 100
+ });
+
+ if (!files.length) {
+ return refuse('it changes no files, so there is nothing to verify or merge.');
+ }
+ // Refuses when the file list is truncated or disagrees with the PR.
+ if (files.length >= LISTFILES_CAP || files.length !== pr.changed_files) {
+ return refuse(
+ `it reports ${pr.changed_files} changed files but the API listed ${files.length}, ` +
+ 'so the file list is truncated and I cannot verify the folder scope. A maintainer must merge this one.'
+ );
+ }
+ if (pr.changed_files > MAX_CHANGED_FILES) {
+ return refuse(`it changes ${pr.changed_files} files; delegated merges are capped at ${MAX_CHANGED_FILES}.`);
+ }
+
+ const deniedFiles = [];
+ const outsideFiles = [];
+
+ for (const file of files) {
+ for (const path of pathsFor(file)) {
+ if (isDenied(path)) {
+ deniedFiles.push(path);
+ } else if (!isGranted(path, grants)) {
+ outsideFiles.push(path);
+ }
+ }
+ }
+
+ if (deniedFiles.length) {
+ core.error(`@${commenter} attempted a delegated merge touching protected paths: ${deniedFiles.join(', ')}`);
+ return refuse(
+ 'it touches paths that are never delegatable, whatever the grants say ' +
+ `(CI, build, scripts or executable files):\n\n${formatList(deniedFiles)}\n\nA maintainer should look at this before it goes any further.`
+ );
+ }
+ if (outsideFiles.length) {
+ return refuse(
+ `${outsideFiles.length} changed path(s) fall outside your grants:\n\n${formatList(outsideFiles)}\n\n` +
+ 'A vendor needs both grants: `resources/profiles//` **and** `resources/profiles/.json`.'
+ );
+ }
+
+ // ---- file modes: rejects symlinks and submodules ----
+ // Fetches the delegatable subtree only; listFiles does not report modes.
+ const headSha = pr.head.sha;
+ const { data: tree } = await github.rest.git.getTree({
+ owner,
+ repo,
+ tree_sha: `${headSha}:${DELEGATABLE_ROOT.replace(/\/$/, '')}`,
+ recursive: 'true'
+ });
+
+ if (tree.truncated) {
+ return refuse('the git tree is too large to verify file modes. A maintainer must merge this one.');
+ }
+
+ // Entry paths are subtree-relative.
+ const modesByPath = new Map(tree.tree.map((entry) => [`${DELEGATABLE_ROOT}${entry.path}`, entry.mode]));
+ const irregularFiles = files
+ .filter((file) => file.status !== 'removed')
+ .map((file) => [file.filename, modesByPath.get(file.filename)])
+ .filter(([, mode]) => !REGULAR_FILE_MODES.has(mode))
+ .map(([path, mode]) => `${path} (mode ${mode || 'missing'})`);
+
+ if (irregularFiles.length) {
+ core.error(`@${commenter} attempted a delegated merge with non-regular files: ${irregularFiles.join(', ')}`);
+ return refuse(
+ `it adds symlinks, submodules or files I cannot verify:\n\n${formatList(irregularFiles)}\n\nA maintainer should look at this before it goes any further.`
+ );
+ }
+
+ // ---- mergeability: waits for GitHub to compute it ----
+ for (let attempt = 0; pr.mergeable === null && attempt < MERGEABLE_ATTEMPTS; attempt += 1) {
+ core.info(`Mergeability not computed yet; retrying in ${MERGEABLE_DELAY_MS}ms.`);
+ await sleep(MERGEABLE_DELAY_MS);
+ ({ data: pr } = await github.rest.pulls.get({
+ owner,
+ repo,
+ pull_number: pr.number
+ }));
+ }
+
+ if (pr.mergeable === null) {
+ return refuse('GitHub is still working out whether it can be merged. Try `/bot merge` again in a minute.');
+ }
+ if (!pr.mergeable) {
+ return refuse(`it is not mergeable (\`${pr.mergeable_state}\`) - most likely a conflict with \`${pr.base.ref}\`.`);
+ }
+
+ // ---- CI on the head commit ----
+ const checkRuns = await github.paginate(github.rest.checks.listForRef, {
+ owner,
+ repo,
+ ref: headSha,
+ filter: 'latest',
+ per_page: 100
+ });
+ const pendingChecks = checkRuns.filter((run) => run.status !== 'completed');
+ const failedChecks = checkRuns.filter((run) => run.status === 'completed' && !OK_CONCLUSIONS.has(run.conclusion));
+
+ if (pendingChecks.length) {
+ return refuse(
+ `${pendingChecks.length} check(s) are still running on \`${headSha.slice(0, 7)}\`:\n\n` +
+ `${formatList(pendingChecks.map((run) => run.name))}\n\nRe-run \`/bot merge\` once they finish.`
+ );
+ }
+ if (failedChecks.length) {
+ return refuse(
+ `${failedChecks.length} check(s) are not green on \`${headSha.slice(0, 7)}\`:\n\n` +
+ formatList(failedChecks.map((run) => `${run.name} (${run.conclusion})`))
+ );
+ }
+
+ const { data: combined } = await github.rest.repos.getCombinedStatusForRef({
+ owner,
+ repo,
+ ref: headSha
+ });
+ // total_count 0 only means there are no legacy statuses.
+ if (combined.total_count > 0 && combined.state !== 'success') {
+ return refuse(
+ `the combined commit status on \`${headSha.slice(0, 7)}\` is \`${combined.state}\`:\n\n` +
+ formatList(combined.statuses.filter((status) => status.state !== 'success')
+ .map((status) => `${status.context} (${status.state})`))
+ );
+ }
+
+ // Requires the check to have actually run, not merely to have not failed.
+ const requiredCheck = checkRuns.find((run) =>
+ run.name === REQUIRED_CHECK &&
+ run.app && run.app.slug === 'github-actions' &&
+ run.status === 'completed' && OK_CONCLUSIONS.has(run.conclusion));
+
+ if (!requiredCheck) {
+ return refuse(
+ `the \`${REQUIRED_CHECK}\` check has not succeeded on \`${headSha.slice(0, 7)}\`. ` +
+ 'If it never ran, a maintainer needs to approve the workflow run first.'
+ );
+ }
+
+ const scopeSummary = `${files.length} file(s), all within:\n${formatList(grants)}`;
+
+ if (dryRun) {
+ core.info('Dry run: every gate passed, not merging.');
+ await react('+1');
+ await say(
+ `@${commenter} **dry run** - this PR passes every gate and I *would* squash-merge it ` +
+ `at \`${headSha.slice(0, 7)}\`.\n\nVerified scope: ${scopeSummary}`
+ );
+ return;
+ }
+
+ // ---- re-validate, then merge ----
+ // An unchanged head SHA means the verified file list still holds.
+ const { data: fresh } = await github.rest.pulls.get({
+ owner,
+ repo,
+ pull_number: pr.number
+ });
+
+ if (fresh.head.sha !== headSha || fresh.base.ref !== pr.base.ref || fresh.state !== 'open' || fresh.merged || fresh.draft) {
+ return refuse('it changed while I was checking it. Nothing was merged - re-run `/bot merge`.');
+ }
+
+ let merged;
+ try {
+ // Pinned to the verified head: a moved head fails with 409.
+ ({ data: merged } = await github.rest.pulls.merge({
+ owner,
+ repo,
+ pull_number: pr.number,
+ sha: headSha,
+ merge_method: MERGE_METHOD,
+ commit_title: `${pr.title} (#${pr.number})`,
+ commit_message:
+ `Merged by /bot merge on behalf of @${commenter} (id ${comment.user.id}).\n` +
+ `Grants: ${grants.join(', ')}\nHead: ${headSha}\n`
+ }));
+ } catch (error) {
+ const hint = {
+ 403: 'the workflow token cannot write to the repository.',
+ 405: 'GitHub refused the merge - branch protection, a required review or check, a newly added CODEOWNERS file, or squash merging being disabled.',
+ 409: `the head commit moved after I verified it (was \`${headSha.slice(0, 7)}\`).`,
+ 422: 'GitHub rejected the merge as invalid.'
+ }[error.status];
+
+ if (!hint) {
+ throw error;
+ }
+
+ await refuse(`${hint}\n\n> ${error.message}\n\nNothing was merged.`);
+ core.setFailed(`Delegated merge failed: ${error.status} ${error.message}`);
+ return;
+ }
+
+ core.info(`Merged #${pr.number} as ${merged.sha}.`);
+ await react('rocket');
+ await say(
+ `@${commenter} squash-merged into \`${pr.base.ref}\` as ${merged.sha}.\n\nVerified scope: ${scopeSummary}`
+ );
+
+ // ---- re-kick the build ----
+ // A GITHUB_TOKEN merge fires no push event, so build_all.yml would
+ // otherwise never see these files.
+ try {
+ await github.rest.actions.createWorkflowDispatch({
+ owner,
+ repo,
+ workflow_id: 'build_all.yml',
+ ref: pr.base.ref
+ });
+ core.info(`Dispatched build_all.yml on ${pr.base.ref}.`);
+ } catch (error) {
+ core.warning(`Merged successfully, but dispatching build_all.yml failed: ${error.message}`);
+ }
diff --git a/AGENTS.md b/AGENTS.md
index fbc624b958..236aa54c05 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -56,6 +56,7 @@ ctest --test-dir ./tests/fff_print
- Add helper functions or utilities only when existing code cannot reasonably be reused. Avoid duplication.
- Keep code concise and clear. Manually simplify AI generated bloated codes before review.
- Include targeted tests or documented verification for behavior changes, especially in slicing logic, profiles, formats, and GUI defaults.
+- For profile changes (`resources/profiles//**`), check that `version` in the sibling `resources/profiles/.json` was bumped.
- For translation changes (`localization/i18n/**/*.po`), check that recurring terms match the [Localization glossary](https://github.com/OrcaSlicer/OrcaSlicer_WIKI/blob/main/guides/localization_glossary.md) for that language.
## Localization & translations
diff --git a/build_release_vs.bat b/build_release_vs.bat
index 3288beda4c..78419dadf5 100644
--- a/build_release_vs.bat
+++ b/build_release_vs.bat
@@ -152,7 +152,7 @@ echo on
set CMAKE_POLICY_VERSION_MINIMUM=3.5
if "%USE_NINJA%"=="1" (
cmake .. -G %CMAKE_GENERATOR% -DORCA_TOOLS=ON %SIG_FLAG% -DBUILD_TESTS=%BUILD_TESTS% -DCMAKE_BUILD_TYPE=%build_type%
- cmake --build . --config %build_type% --target ALL_BUILD
+ cmake --build . --config %build_type% --target all
) else (
cmake .. -G %CMAKE_GENERATOR% -A %arch% -DORCA_TOOLS=ON %SIG_FLAG% -DBUILD_TESTS=%BUILD_TESTS% -DCMAKE_BUILD_TYPE=%build_type%
cmake --build . --config %build_type% --target ALL_BUILD -- -m
diff --git a/deps/TBB/MSVC.cmake b/deps/TBB/MSVC.cmake
new file mode 100644
index 0000000000..d7984bff80
--- /dev/null
+++ b/deps/TBB/MSVC.cmake
@@ -0,0 +1,98 @@
+# Copyright (c) 2020-2021 Intel Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+set(TBB_LINK_DEF_FILE_FLAG ${CMAKE_LINK_DEF_FILE_FLAG})
+set(TBB_DEF_FILE_PREFIX win${TBB_ARCH})
+
+# Workaround for CMake issue https://gitlab.kitware.com/cmake/cmake/issues/18317.
+# TODO: consider use of CMP0092 CMake policy.
+string(REGEX REPLACE "/W[0-4]" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
+
+set(TBB_WARNING_LEVEL $<$:/W4> $<$:/WX>)
+
+# Warning suppression C4324: structure was padded due to alignment specifier
+set(TBB_WARNING_SUPPRESS /wd4324)
+set(TBB_TEST_COMPILE_FLAGS /bigobj)
+
+if (MSVC_VERSION LESS_EQUAL 1900)
+ # Warning suppression C4503 for VS2015 and earlier:
+ # decorated name length exceeded, name was truncated.
+ # More info can be found at
+ # https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-level-1-c4503
+ set(TBB_TEST_COMPILE_FLAGS ${TBB_TEST_COMPILE_FLAGS} /wd4503)
+endif()
+
+set(TBB_LIB_COMPILE_FLAGS -D_CRT_SECURE_NO_WARNINGS /GS)
+set(TBB_COMMON_COMPILE_FLAGS /volatile:iso /FS /EHsc)
+
+# Ignore /WX set through add_compile_options() or added to CMAKE_CXX_FLAGS if TBB_STRICT is disabled.
+if (NOT TBB_STRICT AND COMMAND tbb_remove_compile_flag)
+ tbb_remove_compile_flag(/WX)
+endif()
+
+if (WINDOWS_STORE OR TBB_WINDOWS_DRIVER)
+ set(TBB_COMMON_COMPILE_FLAGS ${TBB_COMMON_COMPILE_FLAGS} /D_WIN32_WINNT=0x0A00)
+ set(TBB_COMMON_LINK_FLAGS -NODEFAULTLIB:kernel32.lib -INCREMENTAL:NO)
+ set(TBB_COMMON_LINK_LIBS OneCore.lib)
+endif()
+
+if (WINDOWS_STORE)
+ if (NOT CMAKE_SYSTEM_VERSION EQUAL 10.0)
+ message(FATAL_ERROR "CMAKE_SYSTEM_VERSION must be equal to 10.0")
+ endif()
+ set(TBB_COMMON_COMPILE_FLAGS ${TBB_COMMON_COMPILE_FLAGS} /ZW /ZW:nostdlib)
+ # CMake define this extra lib, remove it for this build type
+ string(REGEX REPLACE "WindowsApp.lib" "" CMAKE_CXX_STANDARD_LIBRARIES "${CMAKE_CXX_STANDARD_LIBRARIES}")
+
+ if (TBB_NO_APPCONTAINER)
+ set(TBB_LIB_LINK_FLAGS ${TBB_LIB_LINK_FLAGS} -APPCONTAINER:NO)
+ endif()
+endif()
+
+if (TBB_WINDOWS_DRIVER)
+ # Since this is universal driver disable this variable
+ set(CMAKE_SYSTEM_PROCESSOR "")
+ # CMake define list additional libs, remove it for this build type
+ set(CMAKE_CXX_STANDARD_LIBRARIES "")
+ set(TBB_COMMON_COMPILE_FLAGS ${TBB_COMMON_COMPILE_FLAGS} /D _UNICODE /DUNICODE /DWINAPI_FAMILY=WINAPI_FAMILY_APP /D__WRL_NO_DEFAULT_LIB__)
+endif()
+
+if (NOT DEFINED TBB_ENABLE_IPO)
+ if (DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION)
+ set(TBB_ENABLE_IPO ${CMAKE_INTERPROCEDURAL_OPTIMIZATION})
+ else()
+ set(TBB_ENABLE_IPO ON)
+ endif()
+endif()
+
+if (TBB_ENABLE_IPO)
+ if (CMAKE_CXX_COMPILER_ID MATCHES "(Clang|IntelLLVM)")
+ if (CMAKE_SYSTEM_PROCESSOR MATCHES "(x86|AMD64)")
+ set(TBB_COMMON_COMPILE_FLAGS ${TBB_COMMON_COMPILE_FLAGS} -mrtm -mwaitpkg)
+ endif()
+ set(TBB_OPENMP_NO_LINK_FLAG TRUE)
+ set(TBB_IPO_COMPILE_FLAGS $<$>:-flto>)
+ else()
+ set(TBB_IPO_COMPILE_FLAGS $<$>:/GL>)
+ set(TBB_IPO_LINK_FLAGS $<$>:-LTCG> $<$>:-INCREMENTAL:NO>)
+ endif()
+else()
+ if (CMAKE_CXX_COMPILER_ID MATCHES "(Clang|IntelLLVM)" AND CMAKE_SYSTEM_PROCESSOR MATCHES "(x86|AMD64)")
+ set(TBB_COMMON_COMPILE_FLAGS ${TBB_COMMON_COMPILE_FLAGS} -mrtm -mwaitpkg)
+ endif()
+ set(TBB_IPO_COMPILE_FLAGS "")
+ set(TBB_IPO_LINK_FLAGS "")
+endif()
+
+set(TBB_OPENMP_FLAG /openmp)
diff --git a/deps/TBB/TBB.cmake b/deps/TBB/TBB.cmake
index 9b1452d33e..dac2ed63e6 100644
--- a/deps/TBB/TBB.cmake
+++ b/deps/TBB/TBB.cmake
@@ -1,4 +1,6 @@
-if (FLATPAK AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
+if (MSVC)
+ set(_patch_command ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/MSVC.cmake ./cmake/compilers/MSVC.cmake)
+elseif (FLATPAK AND "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU")
set(_patch_command ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/GNU.cmake ./cmake/compilers/GNU.cmake)
else()
set(_patch_command "")
@@ -13,6 +15,8 @@ orcaslicer_add_cmake_project(
-DTBB_BUILD_SHARED=OFF
-DTBB_BUILD_TESTS=OFF
-DTBB_TEST=OFF
+ -DTBB_ENABLE_IPO=OFF
+ -DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
-DCMAKE_DEBUG_POSTFIX=_debug
)
diff --git a/deps/wxWidgets/0001-Clang-CL-fix.patch b/deps/wxWidgets/0001-Clang-CL-fix.patch
new file mode 100644
index 0000000000..23bf23b3f4
--- /dev/null
+++ b/deps/wxWidgets/0001-Clang-CL-fix.patch
@@ -0,0 +1,28 @@
+---
+ build/cmake/wxWidgetsConfig.cmake.in | 10 +++++++++-
+ 1 file changed, 10 insertions(+), 1 deletion(-)
+
+diff --git a/build/cmake/wxWidgetsConfig.cmake.in b/build/cmake/wxWidgetsConfig.cmake.in
+index 1a83f36..70ad8a4 100644
+--- a/build/cmake/wxWidgetsConfig.cmake.in
++++ b/build/cmake/wxWidgetsConfig.cmake.in
+@@ -58,7 +58,16 @@ if(WIN32_MSVC_NAMING)
+ endif()
+ endif()
+
+-include("${CMAKE_CURRENT_LIST_DIR}${wxPLATFORM_LIB_DIR}/@PROJECT_NAME@Targets.cmake")
++if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
++ if (CMAKE_GENERATOR_PLATFORM STREQUAL "ARM64" OR CMAKE_VS_PLATFORM_NAME STREQUAL "ARM64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "^(ARM64|arm64|aarch64)$")
++ set(_wx_clang_msvc_lib_dir "vc_arm64_lib")
++ else()
++ set(_wx_clang_msvc_lib_dir "vc_x64_lib")
++ endif()
++ include("${CMAKE_CURRENT_LIST_DIR}${wxPLATFORM_LIB_DIR}/${_wx_clang_msvc_lib_dir}/@PROJECT_NAME@Targets.cmake")
++else()
++ include("${CMAKE_CURRENT_LIST_DIR}${wxPLATFORM_LIB_DIR}/@PROJECT_NAME@Targets.cmake")
++endif()
+
+ macro(wx_inherit_property source dest name)
+ # property name without _
+--
+2.43.0
diff --git a/deps/wxWidgets/wxWidgets.cmake b/deps/wxWidgets/wxWidgets.cmake
index 1e2cc85f78..07bb31d8be 100644
--- a/deps/wxWidgets/wxWidgets.cmake
+++ b/deps/wxWidgets/wxWidgets.cmake
@@ -28,6 +28,7 @@ orcaslicer_add_cmake_project(
GIT_SHALLOW ON
GIT_SUBMODULES 3rdparty/catch 3rdparty/pcre 3rdparty/libwebp
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} ${JPEG_PKG}
+ PATCH_COMMAND git apply --verbose --ignore-space-change --whitespace=fix ${CMAKE_CURRENT_LIST_DIR}/0001-Clang-CL-fix.patch
CMAKE_ARGS
-DwxBUILD_PRECOMP=ON
${_wx_toolkit}
diff --git a/deps_src/clipper2/CMakeLists.txt b/deps_src/clipper2/CMakeLists.txt
index c604002da7..86c9a9efab 100644
--- a/deps_src/clipper2/CMakeLists.txt
+++ b/deps_src/clipper2/CMakeLists.txt
@@ -37,7 +37,11 @@ target_include_directories(Clipper2
)
if (WIN32)
- target_compile_options(Clipper2 PRIVATE /W4 /WX)
+ if (MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+ target_compile_options(Clipper2 PRIVATE /W4 /WX)
+ elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
+ target_compile_options(Clipper2 PRIVATE /W4)
+ endif()
else()
target_compile_options(Clipper2 PRIVATE -Wall -Wextra -Wpedantic -Werror)
target_link_libraries(Clipper2 PUBLIC -lm)
diff --git a/deps_src/miniz/CMakeLists.txt b/deps_src/miniz/CMakeLists.txt
index e02d8a4885..7e060a180f 100644
--- a/deps_src/miniz/CMakeLists.txt
+++ b/deps_src/miniz/CMakeLists.txt
@@ -11,6 +11,8 @@ add_library(miniz_static STATIC
if(${CMAKE_C_COMPILER_ID} STREQUAL "GNU")
target_compile_definitions(miniz_static PRIVATE _GNU_SOURCE)
+elseif (CMAKE_C_COMPILER_ID STREQUAL "Clang" AND CMAKE_C_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
+ target_compile_options(miniz_static PRIVATE /clang:-Wno-error=incompatible-pointer-types)
endif()
target_link_libraries(miniz INTERFACE miniz_static)
diff --git a/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po b/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po
index 0d777ec32e..595e46b8cf 100644
--- a/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po
+++ b/localization/i18n/pt_BR/OrcaSlicer_pt_BR.po
@@ -217,7 +217,6 @@ msgstr "Os filamentos %s são duros e quebradiços, podendo se romper no AMS. E
msgid "%s has a risk of nozzle clogging when using 0.4, 0.6, 0.8mm high-flow nozzles. Use with caution."
msgstr "%s apresenta risco de entupimento do bico ao utilizar bicos de alto fluxo de 0,4, 0,6 ou 0,8 mm. Use com cautela."
-# AI Translated
#, c-format, boost-format
msgid "%s may fail to load or unload due to the Filament Track Switch. If you wish to continue."
msgstr "%s pode falhar ao carregar ou descarregar devido ao Filament Track Switch. Se você deseja continuar."
@@ -347,7 +346,6 @@ msgstr "Leitura "
msgid "Please wait"
msgstr "Por favor, aguarde"
-# AI Translated
msgid "Reading"
msgstr "Lendo"
@@ -700,7 +698,6 @@ msgstr "Redefinir posição"
msgid "Reset rotation"
msgstr "Redefinir rotação"
-# AI Translated
msgid "World"
msgstr "Mundo"
@@ -988,7 +985,6 @@ msgstr "Plano de corte com cavidade é inválido"
msgid "Connector"
msgstr "Conector"
-# AI Translated
#, boost-format
msgid ""
"Objects(%1%) have duplicated connectors. Some connectors may be missing in slicing result.\n"
@@ -2032,7 +2028,6 @@ msgstr ""
"\n"
"Se você não usava o Bambu Cloud para sincronizar perfis, esta mudança não afeta você e você pode ignorar esta mensagem com segurança."
-# AI Translated
msgid "Profile syncing change"
msgstr "Alteração de sincronização de perfil"
@@ -3429,7 +3424,7 @@ msgid "AMS has not been initialized. Please initialize it before use."
msgstr "O AMS não foi inicializado. Por favor, inicialize-o antes de usar."
msgid "Changing fan speed during printing may affect print quality, please choose carefully."
-msgstr "Mudar a velocidade do ventilador durante a impressão pode afetar a qualidade da impressão. Escolha com cuidado."
+msgstr "Mudar a velocidade da ventoinha durante a impressão pode afetar a qualidade da impressão. Escolha com cuidado."
msgid "Change Anyway"
msgstr "Mudar Mesmo Assim"
@@ -3441,7 +3436,7 @@ msgid "Filter"
msgstr "Filtrar"
msgid "Enabling filtration redirects the right fan to filter gas, which may reduce cooling performance."
-msgstr "Ativar a filtragem redireciona o ventilador direito para filtrar o gás, o que pode reduzir o desempenho de resfriamento."
+msgstr "Ativar a filtragem redireciona a ventoinha direito para filtrar o gás, o que pode reduzir o desempenho de resfriamento."
msgid "Enabling filtration during printing may reduce cooling and affect print quality. Please choose carefully."
msgstr "Habilitar a filtragem durante a impressão pode reduzir o resfriamento e afetar a qualidade da impressão. Escolha com cuidado."
@@ -3474,7 +3469,7 @@ msgid "Top"
msgstr "Topo"
msgid "The fan controls the temperature during printing to improve print quality. The system automatically adjusts the fan's switch and speed according to different printing materials."
-msgstr "O ventilador controla a temperatura durante a impressão para melhorar a qualidade da impressão. O sistema ajusta automaticamente a ativação e a velocidade do ventilador de acordo com os diferentes materiais de impressão."
+msgstr "A ventoinha controla a temperatura durante a impressão para melhorar a qualidade da impressão. O sistema ajusta automaticamente a ativação e a velocidade da ventoinha de acordo com os diferentes materiais de impressão."
msgid "Cooling mode is suitable for printing PLA/PETG/TPU materials and filters the chamber air."
msgstr "O modo de resfriamento é adequado para impressão com materiais PLA/PETG/TPU e filtra o ar da câmara."
@@ -4798,7 +4793,7 @@ msgid "Pause (AMS offline)"
msgstr "Pausa (AMS offline)"
msgid "Pause (low speed of the heatbreak fan)"
-msgstr "Pausa (baixa velocidade do ventilador do heatbreak)"
+msgstr "Pausa (baixa velocidade da ventoinha do heatbreak)"
msgid "Pause (chamber temperature control problem)"
msgstr "Pausa (problema no controle de temperatura da câmara)"
@@ -4922,7 +4917,7 @@ msgstr "Para garantir sua segurança, certas tarefas de processamento (como o la
#, c-format, boost-format
msgid "The chamber temperature is too high, which may cause the filament to soften. Please wait until the chamber temperature drops below %d℃. You may open the front door or enable fans to cool down."
-msgstr "A temperatura da câmara está muito alta, o que pode causar o amolecimento do filamento. Aguarde até que a temperatura da câmara caia abaixo de %d℃. Você pode abrir a porta frontal ou ligar os ventiladores para resfriar."
+msgstr "A temperatura da câmara está muito alta, o que pode causar o amolecimento do filamento. Aguarde até que a temperatura da câmara caia abaixo de %d℃. Você pode abrir a porta frontal ou ligar as ventoinhas para resfriar."
#, c-format, boost-format
msgid "AMS temperature is too high, which may cause the filament to soften. Please wait until the AMS temperature drops below %d℃."
@@ -5208,7 +5203,7 @@ msgid "Jerk"
msgstr "Jerk"
msgid "Fan Speed"
-msgstr "Velocidade do Ventilador"
+msgstr "Velocidade da Ventoinha"
msgid "Flow"
msgstr "Fluxo"
@@ -5314,7 +5309,7 @@ msgid "Flow: "
msgstr "Fluxo: "
msgid "Fan: "
-msgstr "Ventilador: "
+msgstr "Ventoinha: "
msgid "Temperature: "
msgstr "Temperatura: "
@@ -5350,7 +5345,7 @@ msgid "Flow rate"
msgstr "Taxa de fluxo"
msgid "Fan speed"
-msgstr "Velocidade do ventilador"
+msgstr "Velocidade da ventoinha"
msgid "Time"
msgstr "Tempo"
@@ -5464,7 +5459,7 @@ msgid "Jerk (mm/s)"
msgstr "Jerk (mm/s)"
msgid "Fan speed (%)"
-msgstr "Velocidade do ventilador (%)"
+msgstr "Velocidade da ventoinha (%)"
msgid "Temperature (℃)"
msgstr "Temperatura (℃)"
@@ -7368,12 +7363,11 @@ msgstr "Inferior"
msgid "Plugin Selection"
msgstr "Seleção de plugins"
-# AI Translated
msgid ""
"No plugins capabilities available for this type.\n"
"Enable or install some to use."
msgstr ""
-"Nenhum recurso de plugins disponível para este tipo.\n"
+"Nenhuma capacidade de plugin disponível para este tipo.\n"
"Ative ou instale algum para usar."
msgid "There is stringing-prone filament in the current print job. Enabling nozzle clumping detection now may degrade print quality. Are you sure you want to enable it?"
@@ -9758,7 +9752,7 @@ msgid "Unable to automatically match to suitable filament. Please click to manua
msgstr "Não foi possível encontrar automaticamente um filamento adequado. Clique para selecionar manualmente."
msgid "Install toolhead enhanced cooling fan to prevent filament softening."
-msgstr "Instale um ventilador de resfriamento aprimorado no cabeçote de impressão para evitar o amolecimento do filamento."
+msgstr "Instale uma ventoinha de resfriamento aprimorado no cabeçote de impressão para evitar o amolecimento do filamento."
msgid "Smooth Cool Plate"
msgstr "Placa Fria Lisa"
@@ -10382,25 +10376,25 @@ msgid "Cooling for specific layer"
msgstr "Resfriamento para camada específica"
msgid "Part cooling fan"
-msgstr "Ventilador de resfriamento de peças"
+msgstr "Ventoinha de resfriamento de peças"
msgid "Min fan speed threshold"
-msgstr "Limiar de velocidade mínima do ventilador"
+msgstr "Limiar de velocidade mínima da ventoinha"
msgid "The part cooling fan will run at the minimum fan speed when the estimated layer time is longer than the threshold value. When the layer time is shorter than the threshold, the fan speed will be interpolated between the minimum and maximum fan speed according to layer printing time."
-msgstr "O ventilador de resfriamento de peças irá girar na velocidade mínima quando o tempo estimado da camada for mais longo do que o valor de limiar. Quando o tempo da camada for mais curto que o limiar, a velocidade do ventilador é interpolada entre a velocidade mínima e máxima de acordo com o tempo de impressão da camada."
+msgstr "A ventoinha de resfriamento de peças irá girar na velocidade mínima quando o tempo estimado da camada for mais longo do que o valor de limiar. Quando o tempo da camada for mais curto que o limiar, a velocidade da ventoinha é interpolada entre a velocidade mínima e máxima de acordo com o tempo de impressão da camada."
msgid "Max fan speed threshold"
-msgstr "Limiar de velocidade máxima do ventilador"
+msgstr "Limiar de velocidade máxima da ventoinha"
msgid "The part cooling fan will run at maximum speed when the estimated layer time is shorter than the threshold value."
-msgstr "O ventilador de resfriamento de peças irá girar na velocidade máxima quando o tempo estimado da camada for mais curto que o limiar."
+msgstr "A ventoinha de resfriamento de peças irá girar na velocidade máxima quando o tempo estimado da camada for mais curto que o limiar."
msgid "Auxiliary part cooling fan"
-msgstr "Ventilador auxiliar de resfriamento de peças"
+msgstr "Ventoinha auxiliar de resfriamento de peças"
msgid "Exhaust fan"
-msgstr "Ventilador de exaustão"
+msgstr "Ventoinha de exaustão"
msgid "During print"
msgstr "Durante a impressão"
@@ -10450,10 +10444,10 @@ msgid "G-code flavor is switched"
msgstr "Tipo de G-code está trocado"
msgid "Cooling Fan"
-msgstr "Ventilador de resfriamento"
+msgstr "Ventoinha de resfriamento"
msgid "Fan speed-up time"
-msgstr "Tempo de aceleração do ventilador"
+msgstr "Tempo de aceleração da ventoinha"
msgid "Extruder Clearance"
msgstr "Folga da extrusora"
@@ -11770,7 +11764,6 @@ msgstr "Erro de agrupamento: "
msgid " can not be placed in the "
msgstr " não pode ser colocado na "
-# AI Translated
msgid "Group error in manual mode. Please check nozzle count or regroup."
msgstr "Erro de agrupamento no modo manual. Por favor, verifique o número de bicos ou reagrupe."
@@ -12096,7 +12089,6 @@ msgstr "A contração de filamento não será usada porque a contração dos fil
msgid "Generating skirt & brim"
msgstr "Gerando saia e borda"
-# AI Translated
msgid ""
"Per-object skirts cannot fit between the objects in By object print sequence.\n"
"\n"
@@ -12277,9 +12269,8 @@ msgstr "API Key"
msgid "HTTP digest"
msgstr "Digest HTTP"
-# AI Translated
msgid "Configuration for the plugin capabilities this preset uses, overriding the global Capabilities configuration. Stored as a raw JSON array and edited through the dialog behind the button, never typed in directly."
-msgstr "Configuração dos recursos de plugin que esta predefinição usa, substituindo a configuração global de Recursos. Armazenada como um array JSON bruto e editada por meio da caixa de diálogo atrás do botão, nunca digitada diretamente."
+msgstr "Configuração das capacidades de plugin que esta predefinição usa, substituindo a configuração global de Capacidades. Armazenada como um array JSON bruto e editada por meio da caixa de diálogo atrás do botão, nunca digitada diretamente."
msgid "Avoid crossing walls"
msgstr "Evitar atravessar paredes"
@@ -12420,26 +12411,26 @@ msgid "Force cooling for overhangs and bridges"
msgstr "Resfriamento forçado para saliências e pontes"
msgid "Enable this option to allow adjustment of the part cooling fan speed for specifically for overhangs, internal and external bridges. Setting the fan speed specifically for these features can improve overall print quality and reduce warping."
-msgstr "Habilite esta opção para permitir o ajuste da velocidade do ventilador de resfriamento de peças especificamente para saliências, pontes internas e externas. Definir a velocidade do ventilador especificamente para esses recursos pode melhorar a qualidade geral da impressão e reduzir a deformação."
+msgstr "Habilite esta opção para permitir o ajuste da velocidade da ventoinha de resfriamento de peças especificamente para saliências, pontes internas e externas. Definir a velocidade da ventoinha especificamente para esses recursos pode melhorar a qualidade geral da impressão e reduzir a deformação."
msgid "Overhangs and external bridges fan speed"
-msgstr "Velocidade do ventilador para saliências e pontes externas"
+msgstr "Velocidade da ventoinha para saliências e pontes externas"
msgid ""
"Use this part cooling fan speed when printing bridges or overhang walls with an overhang threshold that exceeds the value set in the 'Overhangs cooling threshold' parameter above. Increasing the cooling specifically for overhangs and bridges can improve the overall print quality of these features.\n"
"\n"
"Please note, this fan speed is clamped on the lower end by the minimum fan speed threshold set above. It is also adjusted upwards up to the maximum fan speed threshold when the minimum layer time threshold is not met."
msgstr ""
-"Use esta parte da velocidade do ventilador de resfriamento ao imprimir pontes ou paredes salientes com um limite de saliência que exceda o valor definido no parâmetro 'Limiar de resfriamento de saliências' acima. Aumentar o resfriamento especificamente para saliências e pontes pode melhorar a qualidade geral de impressão desses recursos.\n"
+"Use esta parte da velocidade da ventoinha de resfriamento ao imprimir pontes ou paredes salientes com um limite de saliência que exceda o valor definido no parâmetro 'Limiar de resfriamento de saliências' acima. Aumentar o resfriamento especificamente para saliências e pontes pode melhorar a qualidade geral de impressão desses recursos.\n"
"\n"
-"Observe que esta velocidade do ventilador é fixada na extremidade inferior pelo limiar mínimo de velocidade do ventilador definido acima. Ela também é ajustada para cima até o limiar máximo de velocidade do ventilador quando o limiar mínimo de tempo da camada não é atingido."
+"Observe que esta velocidade da ventoinha é fixada na extremidade inferior pelo limiar mínimo de velocidade da ventoinha definido acima. Ela também é ajustada para cima até o limiar máximo de velocidade da ventoinha quando o limiar mínimo de tempo da camada não é atingido."
msgid "Overhang cooling activation threshold"
msgstr "Limiar de ativação de resfriamento de saliência"
#, no-c-format, no-boost-format
msgid "When the overhang exceeds this specified threshold, force the cooling fan to run at the 'Overhang Fan Speed' set below. This threshold is expressed as a percentage, indicating the portion of each line's width that is unsupported by the layer beneath it. Setting this value to 0% forces the cooling fan to run for all outer walls, regardless of the overhang degree."
-msgstr "Quando a saliência excede esse limiar especificado, força o ventilador de resfriamento a funcionar na 'Velocidade da ventoinha de saliência' definida abaixo. Esse limiar é expresso como uma porcentagem, indicando a parte da largura de cada linha que não é suportada pela camada abaixo dela. Definir esse valor como 0% força o ventilador de resfriamento a funcionar para todas as paredes externas, independentemente do grau de saliência."
+msgstr "Quando a saliência excede esse limiar especificado, força a ventoinha de resfriamento a funcionar na 'Velocidade da ventoinha de saliência' definida abaixo. Esse limiar é expresso como uma porcentagem, indicando a parte da largura de cada linha que não é suportada pela camada abaixo dela. Definir esse valor como 0% força a ventoinha de resfriamento a funcionar para todas as paredes externas, independentemente do grau de saliência."
msgid "External bridge infill direction"
msgstr "Direção de preenchimento de ponte externa"
@@ -13026,11 +13017,9 @@ msgstr ""
msgid "As object list"
msgstr "Como lista de objetos"
-# AI Translated
msgid "Best of all (shortest path)"
msgstr "Melhor de todas (caminho mais curto)"
-# AI Translated
msgid "Snake"
msgstr "Serpentina"
@@ -13038,7 +13027,7 @@ msgid "Slow printing down for better layer cooling"
msgstr "Diminuir a velocidade de impressão para melhor resfriamento de camada"
msgid "Enable this option to slow printing speed down to ensure that the final layer time is not shorter than the layer time threshold in \"Max fan speed threshold\", so that the layer can be cooled for a longer time. This can improve the quality for small details."
-msgstr "Ative esta opção para diminuir a velocidade de impressão para que o tempo da camada final não seja menor do que o limiar de tempo da camada em \"Limiar de velocidade máxima do ventilador\", para que a camada possa ser resfriada um tempo mais longo. Isso pode melhorar a qualidade para detalhes pequenos."
+msgstr "Ative esta opção para diminuir a velocidade de impressão para que o tempo da camada final não seja menor do que o limiar de tempo da camada em \"Limiar de velocidade máxima da ventoinha\", para que a camada possa ser resfriada um tempo mais longo. Isso pode melhorar a qualidade para detalhes pequenos."
msgid "Normal printing"
msgstr "Impressão normal"
@@ -13093,16 +13082,16 @@ msgid "Enable this to override the fan speed set in custom G-code after print co
msgstr "Habilite para substituir a velocidade da ventoinha definida no G-code personalizado após a conclusão da impressão."
msgid "Speed of exhaust fan during printing. This speed will override the speed in filament custom G-code."
-msgstr "Velocidade do ventilador de exaustão durante a impressão. Esta velocidade substituirá a velocidade no G-code personalizado do filamento."
+msgstr "Velocidade da ventoinha de exaustão durante a impressão. Esta velocidade substituirá a velocidade no G-code personalizado do filamento."
msgid "Speed of exhaust fan after printing completes."
-msgstr "Velocidade do ventilador de exaustão após a conclusão da impressão."
+msgstr "Velocidade da ventoinha de exaustão após a conclusão da impressão."
msgid "No cooling for the first"
msgstr "Sem resfriamento para as primeiras"
msgid "Turn off all cooling fans for the first few layers. This can be used to improve build plate adhesion."
-msgstr "Desligar todos os ventiladores de resfriamento para as primeiras camadas. Isso pode ser usado para obter uma melhor adesão à placa de impressão."
+msgstr "Desligar todos as ventoinhas de resfriamento para as primeiras camadas. Isso pode ser usado para obter uma melhor adesão à placa de impressão."
msgid "Don't support bridges"
msgstr "Não suportar pontes"
@@ -13278,11 +13267,9 @@ msgstr "Densidade da superfície superior"
msgid "Density of top surface layer. A value of 100% creates a fully solid, smooth top layer. Reducing this value results in a textured top surface, according to the chosen top surface pattern. A value of 0% will result in only the walls on the top layer being created. Intended for aesthetic or functional purposes, not to fix issues such as over-extrusion."
msgstr "Densidade da camada superior. Um valor de 100% cria uma camada superior totalmente sólida e lisa. Reduzir esse valor resulta em uma superfície superior texturizada, de acordo com o padrão de superfície superior escolhido. Um valor de 0% resultará na criação apenas das paredes da camada superior. Destinado a fins estéticos ou funcionais, não para corrigir problemas como extrusão excessiva."
-# AI Translated
msgid "Top surface expansion"
msgstr "Expansão da superfície superior"
-# AI Translated
msgid ""
"Expands the top surfaces by this distance to connect distinct top surfaces and fill gaps.\n"
"Useful for cases where the top surface is interrupted by a raised feature, such as text on a plane. Expanding it removes the holes beneath these features and creates a continuous path with a better finish for printing on top. The expansion is applied to the original top surface, before any other processing such as bridging or overhang detection."
@@ -13290,11 +13277,9 @@ msgstr ""
"Expande as superfícies superiores por esta distância para conectar superfícies superiores distintas e preencher lacunas.\n"
"Útil para casos em que a superfície superior é interrompida por um recurso elevado, como um texto sobre um plano. Expandi-la remove os buracos sob esses recursos e cria um caminho contínuo com melhor acabamento para imprimir por cima. A expansão é aplicada à superfície superior original, antes de qualquer outro processamento, como detecção de ponte ou de saliência."
-# AI Translated
msgid "Top expansion wall margin"
msgstr "Margem de parede da expansão superior"
-# AI Translated
msgid ""
"Using “Top surface expansion” may cause a surface that did not previously touch the model's outer walls to now do so.\n"
"This can cause contraction marks (such as the hull line) on the outer walls.\n"
@@ -13304,11 +13289,9 @@ msgstr ""
"Isso pode causar marcas de contração (como a linha do casco) nas paredes externas.\n"
"Ao adicionar uma pequena margem, essa contração não ocorrerá diretamente nas paredes, evitando assim uma marca visível."
-# AI Translated
msgid "Top expansion direction"
msgstr "Direção da expansão superior"
-# AI Translated
msgid ""
"Direction in which the top surface expansion grows.\n"
" - Inward grows into the holes and gaps left by features rising from the middle of a top surface.\n"
@@ -13335,11 +13318,9 @@ msgstr "Padrão de superfície inferior"
msgid "This is the line pattern of bottom surface infill, not including bridge infill."
msgstr "Este é o padrão de linha do preenchimento da superfície inferior, não incluindo o preenchimento de ponte."
-# AI Translated
msgid "Bottom surface density"
msgstr "Densidade da superfície inferior"
-# AI Translated
msgid ""
"Density of the bottom surface layer. Intended for aesthetic or functional purposes, not to fix issues such as over-extrusion.\n"
"WARNING: Lowering this value may negatively affect bed adhesion."
@@ -13347,31 +13328,27 @@ msgstr ""
"Densidade da camada da superfície inferior. Destinada a fins estéticos ou funcionais, não a corrigir problemas como sobre-extrusão.\n"
"AVISO: reduzir este valor pode afetar negativamente a aderência à mesa."
-# AI Translated
msgid "Top surface fill order"
msgstr "Ordem de preenchimento da superfície superior"
-# AI Translated
msgid ""
"Direction in which top surfaces are filled when using a center-based pattern (Concentric, Archimedean Chords, Octagram Spiral).\n"
"Outward starts at the center of the surface, so any excess material is pushed towards the edge where it is least visible. Inward starts at the edge and ends with the tight curves at the center.\n"
"Default uses shortest-path ordering, which may run in either direction."
msgstr ""
-"Direção em que as superfícies superiores são preenchidas ao usar um padrão baseado no centro (Concêntrico, Cordas de Arquimedes, Espiral Octograma).\n"
+"Direção em que as superfícies superiores são preenchidas ao usar um padrão baseado no centro (Concêntrico, Cordas de Arquimedes, Espiral de Octograma).\n"
"Para fora começa no centro da superfície, de modo que qualquer excesso de material seja empurrado em direção à borda, onde é menos visível. Para dentro começa na borda e termina com as curvas fechadas no centro.\n"
"O padrão usa a ordenação de caminho mais curto, que pode seguir em qualquer direção."
-# AI Translated
msgid "Bottom surface fill order"
msgstr "Ordem de preenchimento da superfície inferior"
-# AI Translated
msgid ""
"Direction in which bottom surfaces are filled when using a center-based pattern (Concentric, Archimedean Chords, Octagram Spiral).\n"
"Inward starts each surface with the wider outer curves, which improves first layer adhesion on build plates where the tight curves at the center may not stick. Outward starts at the center, pushing any excess material towards the edge.\n"
"Default uses shortest-path ordering, which may run in either direction."
msgstr ""
-"Direção em que as superfícies inferiores são preenchidas ao usar um padrão baseado no centro (Concêntrico, Cordas de Arquimedes, Espiral Octograma).\n"
+"Direção em que as superfícies inferiores são preenchidas ao usar um padrão baseado no centro (Concêntrico, Cordas de Arquimedes, Espiral de Octograma).\n"
"Para dentro começa cada superfície com as curvas externas mais largas, o que melhora a aderência da primeira camada em mesas onde as curvas fechadas no centro podem não aderir. Para fora começa no centro, empurrando qualquer excesso de material em direção à borda.\n"
"O padrão usa a ordenação de caminho mais curto, que pode seguir em qualquer direção."
@@ -13399,19 +13376,15 @@ msgstr "Limiar de pequenos perímetros"
msgid "This sets the threshold for small perimeter length. Default threshold is 0mm."
msgstr "Isso define o limiar para o comprimento do perímetro pequeno. O limiar padrão é 0 mm."
-# AI Translated
msgid "Small support perimeters"
msgstr "Pequenos perímetros de suporte"
-# AI Translated
msgid "Same as \"Small perimeters\", but for supports. This separate setting will affect the speed of support for areas <= `small_support_perimeter_threshold`. If expressed as a percentage (for example: 80%), it will be calculated on the support or support interface speed setting above. Set to zero for auto."
msgstr "Igual a \"Pequenos perímetros\", mas para suportes. Esta configuração separada afetará a velocidade do suporte para áreas <= `small_support_perimeter_threshold`. Se expressa como uma porcentagem (por exemplo: 80%), será calculada com base na configuração de velocidade de suporte ou de interface de suporte acima. Defina como zero para automático."
-# AI Translated
msgid "Small support perimeters threshold"
-msgstr "Limite de pequenos perímetros de suporte"
+msgstr "Limiar de pequenos perímetros de suporte"
-# AI Translated
msgid "This sets the threshold for small support perimeter length. The default threshold is 0mm."
msgstr "Isto define o limite para o comprimento de pequenos perímetros de suporte. O limite padrão é 0mm."
@@ -13603,7 +13576,6 @@ msgstr ""
msgid "Enable adaptive pressure advance within features (beta)"
msgstr "Habilitar pressure advance adaptativo nos recursos (beta)"
-# AI Translated
msgid ""
"Enable adaptive PA whenever there are flow changes in a feature, such as line width changes in a corner or overhangs.\n"
"\n"
@@ -13635,10 +13607,10 @@ msgid "Default line width if other line widths are set to 0. If expressed as a %
msgstr "Largura de linha padrão se outras larguras de linha estiverem definidas como 0. Se expresso como %, será calculado sobre o diâmetro do bico."
msgid "Keep fan always on"
-msgstr "Manter o ventilador sempre ligado"
+msgstr "Manter a ventoinha sempre ligado"
msgid "Enabling this setting means that part cooling fan will never stop entirely and will instead run at least at minimum speed to reduce the frequency of starting and stopping."
-msgstr "Habilitar esta configuração significa que o ventilador de resfriamento da peça nunca será desligado completamente e funcionará pelo menos na velocidade mínima para reduzir a frequência de inícios e paradas."
+msgstr "Habilitar esta configuração significa que a ventoinha de resfriamento da peça nunca será desligado completamente e funcionará pelo menos na velocidade mínima para reduzir a frequência de inícios e paradas."
msgid "Don't slow down outer walls"
msgstr "Não desacelerar as paredes externas"
@@ -13658,7 +13630,7 @@ msgid "Layer time"
msgstr "Tempo da camada"
msgid "The part cooling fan will be enabled for layers where the estimated time is shorter than this value. Fan speed is interpolated between the minimum and maximum fan speeds according to layer printing time."
-msgstr "O ventilador de resfriamento de peças será ativado para camadas cujo tempo estimado seja mais curto que esse valor. A velocidade do ventilador é interpolada entre as velocidades mínima e máxima do ventilador de acordo com o tempo de impressão da camada."
+msgstr "A ventoinha de resfriamento de peças será ativado para camadas cujo tempo estimado seja mais curto que esse valor. A velocidade da ventoinha é interpolada entre as velocidades mínima e máxima da ventoinha de acordo com o tempo de impressão da camada."
msgid "s"
msgstr "s"
@@ -13706,7 +13678,6 @@ msgstr "Temperatura de purga"
msgid "Temperature when flushing filament. 0 indicates the upper bound of the recommended nozzle temperature range."
msgstr "Temperatura ao purgar filamento. 0 indica o limite superior da faixa de temperatura recomendada para o bico."
-# AI Translated
msgid "Flush temperature used in fast purge mode."
msgstr "Temperatura de purga usada no modo de purga rápida."
@@ -13972,11 +13943,9 @@ msgstr "Filamento imprimível"
msgid "The filament is printable in extruder."
msgstr "O filamento é imprimível na extrusora."
-# AI Translated
msgid "Filament-extruder compatibility"
msgstr "Compatibilidade filamento-extrusora"
-# AI Translated
msgid "A single 32-bit int encoding the compatibility level of a filament across all extruders (up to 10). Every 3 bits represent one extruder (bits [3*i, 3*i+2] for extruder i). 0: printable, 1: error, 2: critical warning, 3: warning, 4-7: reserved."
msgstr "Um único inteiro de 32 bits que codifica o nível de compatibilidade de um filamento em todas as extrusoras (até 10). Cada 3 bits representam uma extrusora (bits [3*i, 3*i+2] para a extrusora i). 0: imprimível, 1: erro, 2: aviso crítico, 3: aviso, 4-7: reservado."
@@ -14016,11 +13985,9 @@ msgstr "Direção do preenchimento sólido"
msgid "Angle for solid infill pattern, which controls the start or main direction of line."
msgstr "Ângulo para padrão de preenchimento sólido, que controla a direção inicial ou principal da linha."
-# AI Translated
msgid "Top layer direction"
msgstr "Direção da camada superior"
-# AI Translated
msgid ""
"Fixed angle for the top solid infill and ironing lines.\n"
"Set to -1 to follow the default solid infill direction."
@@ -14028,11 +13995,9 @@ msgstr ""
"Ângulo fixo para o preenchimento sólido superior e as linhas de alisamento.\n"
"Defina como -1 para seguir a direção padrão do preenchimento sólido."
-# AI Translated
msgid "Bottom layer direction"
msgstr "Direção da camada inferior"
-# AI Translated
msgid ""
"Fixed angle for the bottom solid infill lines.\n"
"Set to -1 to follow the default solid infill direction."
@@ -14047,11 +14012,9 @@ msgstr "Densidade do preenchimento esparso"
msgid "Density of internal sparse infill, 100% turns all sparse infill into solid infill and internal solid infill pattern will be used."
msgstr "Densidade do preenchimento esparso interno, 100% transforma todo o preenchimento esparso em preenchimento sólido e será usado o padrão de preenchimento sólido interno."
-# AI Translated
msgid "Align directions to model"
msgstr "Alinhar direções ao modelo"
-# AI Translated
msgid ""
"Aligns infill, bridge, ironing, and top/bottom surface directions to follow the model's orientation on the build plate.\n"
"When enabled, these directions rotate together with the model so the printed features keep their intended orientation relative to the part, preserving optimal strength and surface characteristics regardless of how the model is placed."
@@ -14071,11 +14034,9 @@ msgstr "Multilinhas de Preenchimento"
msgid "Using multiple lines for the infill pattern, if supported by infill pattern."
msgstr "Usar múltiplas linhas para o padrão de preenchimento, se suportado pelo padrão de preenchimento."
-# AI Translated
msgid "Z-buckling bias optimization (experimental)"
msgstr "Otimização de tendência à flambagem em Z (experimental)"
-# AI Translated
#, no-c-format, no-boost-format
msgid "Tightens the gyroid wave along the Z (vertical) axis at low infill density to shorten the effective vertical column length and improve Z-axis compression buckling resistance. Filament use is preserved. No effect at ~30% sparse infill density and above. Only applies when Sparse infill pattern is set to Gyroid."
msgstr "Aperta a onda giroide ao longo do eixo Z (vertical) em baixa densidade de preenchimento para encurtar o comprimento efetivo da coluna vertical e melhorar a resistência à flambagem por compressão no eixo Z. O uso de filamento é preservado. Sem efeito em densidade de preenchimento esparso de ~30% ou mais. Aplica-se apenas quando o padrão de Preenchimento esparso está definido como Giroide."
@@ -14198,13 +14159,12 @@ msgstr "Jerk para primeira camada."
msgid "Jerk for travel."
msgstr "Jerk para deslocamento."
-# AI Translated
msgid ""
"Travel jerk of first layer.\n"
"The percentage value is relative to Travel Jerk."
msgstr ""
"Jerk de deslocamento da primeira camada.\n"
-"O valor percentual é relativo ao Jerk de deslocamento."
+"O valor percentual é relativo ao Jerk de Deslocamento."
msgid "Line width of the first layer. If expressed as a %, it will be computed over the nozzle diameter."
msgstr "Largura da linha da primeira camada. Se expresso como uma %, será calculado sobre o diâmetro do bico."
@@ -14243,10 +14203,10 @@ msgid "Nozzle temperature for printing the first layer with this filament"
msgstr "Temperatura do bico para imprimir a primeira camada com este filamento"
msgid "Full fan speed at layer"
-msgstr "Velocidade total do ventilador na camada"
+msgstr "Velocidade total da ventoinha na camada"
msgid "Fan speed will be ramped up linearly from zero at layer \"close_fan_the_first_x_layers\" to maximum at layer \"full_fan_speed_layer\". \"full_fan_speed_layer\" will be ignored if lower than \"close_fan_the_first_x_layers\", in which case the fan will be running at maximum allowed speed at layer \"close_fan_the_first_x_layers\" + 1."
-msgstr "A velocidade do ventilador aumentará linearmente de zero na camada \"close_fan_the_first_x_layers\" para o máximo na camada \"full_fan_speed_layer\". \"full_fan_speed_layer\" será ignorado se for menor que \"close_fan_the_first_x_layers\", caso em que o ventilador funcionará na velocidade máxima permitida na camada \"close_fan_the_first_x_layers\" + 1."
+msgstr "A velocidade da ventoinha aumentará linearmente de zero na camada \"close_fan_the_first_x_layers\" para o máximo na camada \"full_fan_speed_layer\". \"full_fan_speed_layer\" será ignorado se for menor que \"close_fan_the_first_x_layers\", caso em que a ventoinha funcionará na velocidade máxima permitida na camada \"close_fan_the_first_x_layers\" + 1."
msgid "layer"
msgstr "camada"
@@ -14254,7 +14214,6 @@ msgstr "camada"
msgid "First layer fan speed"
msgstr "Velocidade da ventoinha na primeira camada"
-# AI Translated
msgid ""
"Sets an exact fan speed for the first layer, overriding all other cooling settings. Useful for protecting 3D-printed toolhead parts (e.g. Voron-style ABS/ASA ducts) from a hot bed. A small amount of airflow cools the ducts down, without using full cooling that may in certain conditions hurt first-layer adhesion.\n"
"From the second layer onwards, normal cooling resumes.\n"
@@ -14262,44 +14221,44 @@ msgid ""
"Only available when \"No cooling for the first\" is 0.\n"
"Set to -1 to disable it."
msgstr ""
-"Define uma velocidade exata do ventilador para a primeira camada, substituindo todas as outras configurações de resfriamento. Útil para proteger peças impressas em 3D da cabeça da ferramenta (por exemplo, dutos ABS/ASA no estilo Voron) de uma mesa quente. Uma pequena quantidade de fluxo de ar resfria os dutos, sem usar o resfriamento total que pode, em certas condições, prejudicar a aderência da primeira camada.\n"
+"Define uma velocidade exata da ventoinha para a primeira camada, substituindo todas as outras configurações de resfriamento. Útil para proteger peças impressas em 3D da cabeça da ferramenta (por exemplo, dutos ABS/ASA no estilo Voron) de uma mesa aquecida. Uma pequena quantidade de fluxo de ar resfria os dutos, sem usar o resfriamento total que pode, em certas condições, prejudicar a aderência da primeira camada.\n"
"A partir da segunda camada, o resfriamento normal é retomado.\n"
-"Se \"Velocidade total do ventilador na camada\" também estiver definida, o ventilador aumenta suavemente deste valor na primeira camada até o seu alvo na camada escolhida.\n"
+"Se \"Velocidade total da ventoinha na camada\" também estiver definida, a ventoinha aumenta suavemente deste valor na primeira camada até o seu alvo na camada escolhida.\n"
"Disponível apenas quando \"Sem resfriamento nas primeiras\" é 0.\n"
"Defina como -1 para desativá-la."
msgid "Support interface fan speed"
-msgstr "Velocidade do ventilador para interface de suporte"
+msgstr "Velocidade da ventoinha para interface de suporte"
msgid ""
"This part cooling fan speed is applied when printing support interfaces. Setting this parameter to a higher than regular speed reduces the layer binding strength between supports and the supported part, making them easier to separate.\n"
"Set to -1 to disable it.\n"
"This setting is overridden by disable_fan_first_layers."
msgstr ""
-"Esta velocidade do ventilador de resfriamento de peças é aplicada ao imprimir interfaces de suporte. Definir este parâmetro para uma velocidade maior que a normal reduz a força de adesão de camada entre os suportes e a peça suportada, tornando-os mais fáceis de separar.\n"
+"Esta velocidade da ventoinha de resfriamento de peças é aplicada ao imprimir interfaces de suporte. Definir este parâmetro para uma velocidade maior que a normal reduz a força de adesão de camada entre os suportes e a peça suportada, tornando-os mais fáceis de separar.\n"
"Defina como -1 para desabilitá-lo.\n"
"Esta configuração é substituída por disable_fan_first_layers."
msgid "Internal bridges fan speed"
-msgstr "Velocidade do ventilador para pontes internas"
+msgstr "Velocidade da ventoinha para pontes internas"
msgid ""
"The part cooling fan speed used for all internal bridges. Set to -1 to use the overhang fan speed settings instead.\n"
"\n"
"Reducing the internal bridges fan speed, compared to your regular fan speed, can help reduce part warping due to excessive cooling applied over a large surface for a prolonged period of time."
msgstr ""
-"A velocidade do ventilador de resfriamento de peças usada para todas as pontes internas. Defina como -1 para usar as configurações de velocidade do ventilador de sobreposição.\n"
+"A velocidade da ventoinha de resfriamento de peças usada para todas as pontes internas. Defina como -1 para usar as configurações de velocidade da ventoinha de sobreposição.\n"
"\n"
-"Reduzir a velocidade do ventilador das pontes internas, em comparação com a velocidade normal do ventilador, pode ajudar a reduzir a deformação das peças devido ao resfriamento excessivo aplicado sobre uma grande superfície por um período prolongado de tempo."
+"Reduzir a velocidade da ventoinha das pontes internas, em comparação com a velocidade normal da ventoinha, pode ajudar a reduzir a deformação das peças devido ao resfriamento excessivo aplicado sobre uma grande superfície por um período prolongado de tempo."
msgid "Ironing fan speed"
-msgstr "Velocidade do ventilador para alisamento"
+msgstr "Velocidade da ventoinha para alisamento"
msgid ""
"This part cooling fan speed is applied when ironing. Setting this parameter to a lower than regular speed reduces possible nozzle clogging due to the low volumetric flow rate, making the interface smoother.\n"
"Set to -1 to disable it."
msgstr ""
-"Esta velocidade do ventilador de resfriamento de peças é aplicada durante o alisamento. Definir este parâmetro para uma velocidade menor que a normal reduz a possibilidade de entupimento do bico devido ao baixa taxa de fluxo volumétrico, tornando a interface mais suave.\n"
+"Esta velocidade da ventoinha de resfriamento de peças é aplicada durante o alisamento. Definir este parâmetro para uma velocidade menor que a normal reduz a possibilidade de entupimento do bico devido ao baixa taxa de fluxo volumétrico, tornando a interface mais suave.\n"
"Defina como -1 para desabilitá-lo."
msgid "Ironing flow"
@@ -14584,7 +14543,7 @@ msgid "Best auto arranging position in range [0,1] w.r.t. bed shape."
msgstr "Melhor posição de arranjo automático na faixa [0,1] em relação ao formato da mesa."
msgid "Enable this option if machine has auxiliary part cooling fan. G-code command: M106 P2 S(0-255)."
-msgstr "Habilitar esta opção se a máquina tiver ventilador auxiliar de resfriamento de peças. Comando G-code: M106 P2 S(0-255)."
+msgstr "Habilitar esta opção se a máquina tiver ventoinha auxiliar de resfriamento de peças. Comando G-code: M106 P2 S(0-255)."
msgid "Fan direction"
msgstr "Direção da ventoinha"
@@ -14592,7 +14551,6 @@ msgstr "Direção da ventoinha"
msgid "Cooling fan direction of the printer"
msgstr "Direção da ventoinha de resfriamento da impressora"
-# AI Translated
msgid "Both"
msgstr "Ambos"
@@ -14602,9 +14560,9 @@ msgid ""
"It won't move fan commands into the start G-code if the 'only custom start G-code' is activated.\n"
"Use 0 to deactivate."
msgstr ""
-"Ativar o ventilador este número de segundos antes do seu tempo de início alvo (você pode usar frações de segundos). Ele assume aceleração infinita para esta estimativa de tempo e levará em conta apenas os movimentos G1 e G0 (o ajuste de arco não é suportado).\n"
-"Não moverá G-code de comandos do ventilador personalizados (eles funcionam como uma espécie de 'barreira').\n"
-"Não moverá comandos do ventilador para o início do G-code se 'apenas G-code de início personalizado' estiver ativo.\n"
+"Ativar a ventoinha este número de segundos antes do seu tempo de início alvo (você pode usar frações de segundos). Ele assume aceleração infinita para esta estimativa de tempo e levará em conta apenas os movimentos G1 e G0 (o ajuste de arco não é suportado).\n"
+"Não moverá G-code de comandos da ventoinha personalizados (eles funcionam como uma espécie de 'barreira').\n"
+"Não moverá comandos da ventoinha para o início do G-code se 'apenas G-code de início personalizado' estiver ativo.\n"
"Use 0 para desativar."
msgid "Only overhangs"
@@ -14614,15 +14572,15 @@ msgid "Will only take into account the delay for the cooling of overhangs."
msgstr "Levará em conta apenas o atraso para o resfriamento das saliências."
msgid "Fan kick-start time"
-msgstr "Tempo de inicialização do ventilador"
+msgstr "Tempo de inicialização da ventoinha"
msgid ""
"Emit a max fan speed command for this amount of seconds before reducing to target speed to kick-start the cooling fan.\n"
"This is useful for fans where a low PWM/power may be insufficient to get the fan started spinning from a stop, or to get the fan up to speed faster.\n"
"Set to 0 to deactivate."
msgstr ""
-"Emita um comando de velocidade máxima do ventilador por esta quantidade de segundos antes de reduzir para a velocidade alvo para iniciar o ventilador de resfriamento.\n"
-"Isto é útil para ventiladores onde um baixo PWM/potência pode ser insuficiente para fazer o ventilador começar a girar a partir de uma parada, ou para fazer o ventilador alcançar a velocidade mais rapidamente.\n"
+"Emita um comando de velocidade máxima da ventoinha por esta quantidade de segundos antes de reduzir para a velocidade alvo para iniciar a ventoinha de resfriamento.\n"
+"Isto é útil para ventoinhas onde um baixo PWM/potência pode ser insuficiente para fazer a ventoinha começar a girar a partir de uma parada, ou para fazer a ventoinha alcançar a velocidade mais rapidamente.\n"
"Defina como 0 para desativar."
msgid "Minimum non-zero part cooling fan speed"
@@ -14830,19 +14788,15 @@ msgstr "Ângulo de saliência do preenchimento"
msgid "The angle of the infill angled lines. 60° will result in a pure honeycomb."
msgstr "O ângulo das linhas de preenchimento. 60° resultará em um favo de mel puro."
-# AI Translated
msgid "Lightning overhang angle"
-msgstr "Ângulo de saliência Relâmpago"
+msgstr "Ângulo de saliência de Relâmpago"
-# AI Translated
msgid "Maximum overhang angle for Lightning infill support propagation."
msgstr "Ângulo máximo de saliência para a propagação de suporte do preenchimento Relâmpago."
-# AI Translated
msgid "Prune angle"
msgstr "Ângulo de poda"
-# AI Translated
msgid ""
"Controls how aggressively short or unsupported Lightning branches are pruned.\n"
"This angle is converted internally to a per-layer distance."
@@ -14850,11 +14804,9 @@ msgstr ""
"Controla a agressividade com que os ramos Relâmpago curtos ou sem suporte são podados.\n"
"Este ângulo é convertido internamente em uma distância por camada."
-# AI Translated
msgid "Straightening angle"
msgstr "Ângulo de retificação"
-# AI Translated
msgid "Maximum straightening angle used to simplify Lightning branches."
msgstr "Ângulo máximo de retificação usado para simplificar os ramos Relâmpago."
@@ -15205,7 +15157,7 @@ msgstr "Força máxima do eixo Y"
msgid "The allowed maximum output force of Y axis"
msgstr "A força máxima de saída permitida do eixo Y"
-# AI Translated
+#, fuzzy
msgid "N"
msgstr "N"
@@ -15215,6 +15167,7 @@ msgstr "Massa da mesa do eixo Y"
msgid "The machine bed mass load of Y axis"
msgstr "A carga de massa da mesa do equipamento no eixo Y"
+#, fuzzy
msgid "g"
msgstr "G"
@@ -15369,7 +15322,7 @@ msgstr ""
"Para desativar o modelador de entrada, use o tipo Desativar."
msgid "The part cooling fan speed may be increased when auto cooling is enabled. This is the maximum speed for the part cooling fan."
-msgstr "A velocidade do ventilador de resfriamento de peças pode ser aumentada quando o resfriamento automático está habilitado. Este é o limite máximo de velocidade do ventilador de resfriamento de peças."
+msgstr "A velocidade da ventoinha de resfriamento de peças pode ser aumentada quando o resfriamento automático está habilitado. Este é o limite máximo de velocidade da ventoinha de resfriamento de peças."
msgid "The highest printable layer height for the extruder. Used to limit the maximum layer height when enable adaptive layer height."
msgstr "A maior altura de camada imprimível para a extrusora. Usada para limitar a altura máxima da camada quando a altura da camada adaptativa está ativada."
@@ -15432,31 +15385,31 @@ msgid "Applies extrusion rate smoothing only on external perimeters and overhang
msgstr "Aplica suavização de taxa de extrusão somente em perímetros externos e saliências. Isso pode ajudar a reduzir artefatos devido a transições de velocidade bruscas em saliências visíveis externamente sem impactar a velocidade de impressão de recursos que não serão visíveis ao usuário."
msgid "Minimum speed for part cooling fan."
-msgstr "Velocidade mínima para o ventilador de resfriamento de peças."
+msgstr "Velocidade mínima para a ventoinha de resfriamento de peças."
msgid ""
"Speed of auxiliary part cooling fan. Auxiliary fan will run at this speed during printing except the first several layers which is defined by no cooling layers.\n"
"Please enable auxiliary_fan in printer settings to use this feature. G-code command: M106 P2 S(0-255)"
msgstr ""
-"Velocidade do ventilador auxiliar de resfriamento de peças. O ventilador auxiliar funcionará nesta velocidade durante a impressão, exceto nas primeiras camadas, que são definidas por camadas sem resfriamento.\n"
+"Velocidade da ventoinha auxiliar de resfriamento de peças. A ventoinha auxiliar funcionará nesta velocidade durante a impressão, exceto nas primeiras camadas, que são definidas por camadas sem resfriamento.\n"
"\n"
-"Por favor, habilite o ventilador auxiliar nas configurações da impressora para usar esta função. Comando G-code: M106 P2 S(0-255)"
+"Por favor, habilite a ventoinha auxiliar nas configurações da impressora para usar esta função. Comando G-code: M106 P2 S(0-255)"
msgid "For the first"
msgstr "Para as primeiras"
msgid "Set special auxiliary cooling fan for the first certain layers."
-msgstr "Definir um ventilador auxiliar de resfriamento específico para as primeiras camadas."
+msgstr "Definir uma ventoinha auxiliar de resfriamento específico para as primeiras camadas."
msgid ""
"Auxiliary fan speed will be ramped up linearly from layer \"For the first\" to maximum at layer \"Full fan speed at layer\".\n"
"\"Full fan speed at layer\" will be ignored if lower than \"For the first\", in which case the fan will run at maximum allowed speed at layer \"For the first\" + 1."
msgstr ""
-"A velocidade do ventilador auxiliar aumentará linearmente da camada \"Para as primeiras\" até o máximo na camada \"Velocidade total do ventilador na camada\".\n"
-"A \"Velocidade total do ventilador na camada\" será ignorada se for menor que \"Para as primeiras\", caso em que o ventilador funcionará na velocidade máxima permitida na camada \"Para as primeiras\" + 1."
+"A velocidade da ventoinha auxiliar aumentará linearmente da camada \"Para as primeiras\" até o máximo na camada \"Velocidade total da ventoinha na camada\".\n"
+"A \"Velocidade total da ventoinha na camada\" será ignorada se for menor que \"Para as primeiras\", caso em que a ventoinha funcionará na velocidade máxima permitida na camada \"Para as primeiras\" + 1."
msgid "Special auxiliary cooling fan speed, effective only for the first x layers."
-msgstr "Velocidade especial do ventilador de resfriamento auxiliar, efetiva apenas para as primeiras x camadas."
+msgstr "Velocidade especial da ventoinha de resfriamento auxiliar, efetiva apenas para as primeiras x camadas."
msgid "The lowest printable layer height for the extruder. Used to limit the minimum layer height when enable adaptive layer height."
msgstr "A menor altura de camada imprimível para a extrusora. Usada para limitar a altura mínima da camada ao habilitar a altura de camada adaptativa."
@@ -15621,11 +15574,9 @@ msgstr "Este G-code é inserido quando a função de extrusão é trocada. Ele
msgid "Plugins Used"
msgstr "Plugins Utilizados"
-# AI Translated
msgid "Plugin capabilities referenced by this preset, stored as name;uuid;capability."
-msgstr "Recursos de plugin referenciados por esta predefinição, armazenados como name;uuid;capability."
+msgstr "Capacidades de plugin referenciados por esta predefinição, armazenados como name;uuid;capability."
-# AI Translated
msgid "Python plugin(s) invoked at each slicing pipeline step to read and modify intermediate slicing data, including a final G-code post-processing step. Research/experimental."
msgstr "Plugin(s) Python invocado(s) em cada etapa do pipeline de fatiamento para ler e modificar dados intermediários de fatiamento, incluindo uma etapa final de pós-processamento do G-code. Pesquisa/experimental."
@@ -16243,11 +16194,9 @@ msgstr "Preparar todas as extrusoras de impressão"
msgid "If enabled, all printing extruders will be primed at the front edge of the print bed at the start of the print."
msgstr "Se ativado, todos as extrusoras de impressão serão preparados na borda frontal da mesa de impressão no início da impressão."
-# AI Translated
msgid "Toolchange ordering"
msgstr "Ordenação de troca de ferramenta"
-# AI Translated
msgid ""
"Determines the order of tool changes on each layer.\n"
"- Default: Starts with the last used extruder to minimize tool changes.\n"
@@ -16257,7 +16206,6 @@ msgstr ""
"- Padrão: começa com a última extrusora usada para minimizar as trocas de ferramenta.\n"
"- Cíclico: usa uma sequência fixa de ferramentas em cada camada. Isso sacrifica a velocidade em prol de uma melhor qualidade de superfície, pois as trocas de ferramenta extras dão mais tempo para as camadas resfriarem."
-# AI Translated
msgid "Cyclic"
msgstr "Cíclico"
@@ -16638,7 +16586,6 @@ msgstr ""
"\n"
"Se habilitado, este parâmetro também define uma variável G-code chamada chamber_temperature, que pode ser usada para passar a temperatura desejada da câmara para sua macro de início de impressão ou uma macro de absorção de calor como esta: PRINT_START (outras variáveis) CHAMBER_TEMP=[chamber_temperature]. Isso pode ser útil se sua impressora não suportar comandos M141/M191 ou se você desejar lidar com a absorção de calor na macro de início de impressão se nenhum aquecedor de câmara ativo estiver instalado."
-# AI Translated
msgid ""
"This is the chamber temperature at which printing should start, while the chamber continues heating toward the \"Target\" chamber temperature. For example, set the Target to 60 and the Minimal to 50 to begin printing once the chamber reaches 50℃, without waiting for the full 60℃.\n"
"\n"
@@ -16646,11 +16593,11 @@ msgid ""
"\n"
"Unlike the \"Target\" chamber temperature, this option does not emit any M141/M191 commands; it only exposes the value to your custom G-code. It should not exceed the \"Target\" chamber temperature."
msgstr ""
-"Esta é a temperatura da câmara na qual a impressão deve começar, enquanto a câmara continua aquecendo em direção à temperatura da câmara \"Alvo\". Por exemplo, defina o Alvo como 60 e o Mínimo como 50 para começar a imprimir assim que a câmara atingir 50℃, sem esperar pelos 60℃ completos.\n"
+"Esta é a temperatura da câmara na qual a impressão deve começar, enquanto a câmara continua aquecendo em direção à temperatura \"Alvo\" da câmara. Por exemplo, defina o Alvo como 60 e o Mínimo como 50 para começar a imprimir assim que a câmara atingir 50℃, sem esperar pelos 60℃ completos.\n"
"\n"
"Isso define uma variável de G-code chamada chamber_minimal_temperature, que pode ser passada para a sua macro de início de impressão ou uma macro de aquecimento prolongado, assim: PRINT_START (outras variáveis) CHAMBER_MIN_TEMP=[chamber_minimal_temperature].\n"
"\n"
-"Ao contrário da temperatura da câmara \"Alvo\", esta opção não emite nenhum comando M141/M191; ela apenas expõe o valor ao seu G-code personalizado. Não deve exceder a temperatura da câmara \"Alvo\"."
+"Ao contrário da temperatura \"Alvo\" da câmara, esta opção não emite nenhum comando M141/M191; ela apenas expõe o valor ao seu G-code personalizado. Não deve exceder a temperatura \"Alvo\" da câmara."
msgid "Chamber minimal temperature"
msgstr "Temperatura mínima da câmara"
@@ -16694,20 +16641,18 @@ msgstr "Espessura da casca do topo"
msgid "The number of top solid layers is increased when slicing if the thickness calculated by top shell layers is thinner than this value. This can avoid having too thin a shell when layer height is small. 0 means that this setting is disabled and thickness of top shell is determined simply by the number of top shell layers."
msgstr "O número de camadas sólidas superiores é aumentado ao fatiar se a espessura calculada pelas camadas da casca do topo for menor do que este valor. Isso pode evitar que a casca seja muito fina quando a altura da camada é pequena. 0 significa que esta configuração está desativada e a espessura da casca do topo é determinada apenas pelo número de camadas da casca do topo."
-# AI Translated
msgid "Separated infills"
msgstr "Preenchimentos separados"
-# AI Translated
msgid ""
"Centers the internal infill of each part on itself, as if it were sliced on its own, instead of on the whole assembly. Parts that touch or overlap are treated as one body and share a center; separate parts (or distinct 3D objects) each get their own.\n"
"Useful when an assembly groups several objects that should each keep a consistent, self-centered infill.\n"
"Affects line and grid patterns and rotation-template infills.\n"
"Patterns locked to global coordinates (Gyroid, Honeycomb, TPMS, ...) are unaffected."
msgstr ""
-"Centraliza o preenchimento interno de cada peça em si mesma, como se fosse fatiada isoladamente, em vez de no conjunto inteiro. Peças que se tocam ou se sobrepõem são tratadas como um único corpo e compartilham um centro; peças separadas (ou objetos 3D distintos) recebem cada uma o seu próprio.\n"
+"Centraliza o preenchimento interno de cada peça em si mesmo, como se fosse fatiada isoladamente, em vez de no conjunto inteiro. Peças que se tocam ou se sobrepõem são tratadas como um único corpo e compartilham um centro; peças separadas (ou objetos 3D distintos) recebem cada uma o seu próprio.\n"
"Útil quando um conjunto agrupa vários objetos que devem manter, cada um, um preenchimento consistente e autocentrado.\n"
-"Afeta os padrões de linha e grade e os preenchimentos com modelo de rotação.\n"
+"Afeta os padrões de linha e grade e os preenchimentos com gabarito de rotação.\n"
"Os padrões fixados em coordenadas globais (Giroide, Favo de mel, TPMS, ...) não são afetados."
msgid "Center surface pattern on"
@@ -16776,11 +16721,9 @@ msgstr "Multiplicador de purga"
msgid "The actual flushing volumes is equal to the flush multiplier value multiplied by the flushing volumes in the table."
msgstr "Os volumes de purga reais são iguais ao multiplicador de purga multiplicado pelos volumes de purga na tabela."
-# AI Translated
msgid "Flush multiplier (Fast mode)"
msgstr "Multiplicador de purga (Modo rápido)"
-# AI Translated
msgid "The flush multiplier used in fast purge mode."
msgstr "O multiplicador de purga usado no modo de purga rápida."
@@ -16790,13 +16733,12 @@ msgstr "Volume de preparo"
msgid "This is the volume of material to prime the extruder with on the tower."
msgstr "Este é o volume de material para preparar a extrusora na torre."
-# AI Translated
+#,fuzzy
msgid "Prime volume mode"
msgstr "Modo de volume de preparação"
-# AI Translated
msgid "Selects how the wipe-tower prime and flush volumes are computed on multi-extruder printers."
-msgstr "Seleciona como os volumes de preparação e de purga da torre de purga são calculados em impressoras com várias extrusoras."
+msgstr "Seleciona como os volumes de preparação e de purga da torre de purga são computados em impressoras com múltiplas extrusoras."
msgid "Saving"
msgstr "Salvando"
@@ -17116,7 +17058,7 @@ msgid "The maximum volumetric speed for ramming before extruder change, where -1
msgstr "A velocidade volumétrica máxima para compactação antes da troca de extrusor, onde -1 significa usar a velocidade volumétrica máxima."
msgid "To prevent oozing, the nozzle temperature will be cooled during ramming. Note: only a cooldown command and fan activation are triggered, reaching the target temperature is not guaranteed. 0 means disabled."
-msgstr "Para evitar o gotejamento, a temperatura do bico será reduzida durante a compactação. Nota: somente um comando de resfriamento e ativação do ventilador são acionados, não sendo garantido o alcance da temperatura alvo. 0 significa desativado."
+msgstr "Para evitar o gotejamento, a temperatura do bico será reduzida durante a compactação. Nota: somente um comando de resfriamento e ativação da ventoinha são acionados, não sendo garantido o alcance da temperatura alvo. 0 significa desativado."
msgid "The maximum volumetric speed for ramming before a hotend change, where -1 means using the maximum volumetric speed."
msgstr "A velocidade volumétrica máxima para compactação antes de uma troca de hotend, em que -1 significa usar a velocidade volumétrica máxima."
@@ -20744,8 +20686,8 @@ msgid ""
"Auxiliary fan\n"
"Did you know that OrcaSlicer supports Auxiliary part cooling fan?"
msgstr ""
-"Ventilador auxiliar\n"
-"Você sabia que o OrcaSlicer suporta ventilador auxiliar de resfriamento de peças?"
+"Ventoinha auxiliar\n"
+"Você sabia que o OrcaSlicer suporta ventoinha auxiliar de resfriamento de peças?"
#: resources/data/hints.ini: [hint:Air filtration]
msgid ""
@@ -21939,7 +21881,7 @@ msgstr ""
#~ msgstr "Pausado devido à perda do AMS"
#~ msgid "Paused due to low speed of the heat break fan"
-#~ msgstr "Pausado devido a baixa velocidade do ventilador do bloco de aquecimento"
+#~ msgstr "Pausado devido a baixa velocidade da ventoinha do bloco de aquecimento"
#~ msgid "Paused due to chamber temperature control error"
#~ msgstr "Pausado devido a erro no controle de temperatura da câmara"
@@ -22468,20 +22410,20 @@ msgstr ""
#~ msgstr "Forçar resfriamento para saliências e pontes"
#~ msgid "Enable this option to optimize part cooling fan speed for overhang and bridge to get better cooling"
-#~ msgstr "Ative esta opção para otimizar a velocidade do ventilador de resfriamento de peças para saliência e ponte para obter melhor resfriamento"
+#~ msgstr "Ative esta opção para otimizar a velocidade da ventoinha de resfriamento de peças para saliência e ponte para obter melhor resfriamento"
#~ msgid "Fan speed for overhang"
-#~ msgstr "Velocidade do ventilador para saliência"
+#~ msgstr "Velocidade da ventoinha para saliência"
#~ msgid "Force part cooling fan to be this speed when printing bridge or overhang wall which has large overhang degree. Forcing cooling for overhang and bridge can get better quality for these part"
-#~ msgstr "Forçar o ventilador de resfriamento de peças a ser nesta velocidade ao imprimir ponte ou parede saliente que tenha um grande grau de saliência. Forçar o resfriamento para saliência e ponte pode obter melhor qualidade para estas partes"
+#~ msgstr "Forçar a ventoinha de resfriamento de peças a ser nesta velocidade ao imprimir ponte ou parede saliente que tenha um grande grau de saliência. Forçar o resfriamento para saliência e ponte pode obter melhor qualidade para estas partes"
#~ msgid "Cooling overhang threshold"
#~ msgstr "Limiar de resfriamento de saliência"
#, c-format
#~ msgid "Force cooling fan to be specific speed when overhang degree of printed part exceeds this value. Expressed as percentage which indicates how much width of the line without support from lower layer. 0% means forcing cooling for all outer wall no matter how much overhang degree"
-#~ msgstr "Forçar o ventilador de resfriamento a ser uma velocidade específica quando o grau de saliência das peças impressa excede este valor. Expresso como porcentagem, que indica quanto da largura da linha sem suporte da camada inferior. Zero significa forçar o resfriamento para toda a parede externa, não importa quanto seja o grau de saliência"
+#~ msgstr "Forçar a ventoinha de resfriamento a ser uma velocidade específica quando o grau de saliência das peças impressa excede este valor. Expresso como porcentagem, que indica quanto da largura da linha sem suporte da camada inferior. Zero significa forçar o resfriamento para toda a parede externa, não importa quanto seja o grau de saliência"
#~ msgid "Density of external bridges. 100% means solid bridge. Default is 100%."
#~ msgstr "Densidade de pontes externas. 100% significa ponte sólida. O padrão é 100%."
diff --git a/localization/i18n/tr/OrcaSlicer_tr.po b/localization/i18n/tr/OrcaSlicer_tr.po
index 467b3c355b..63d1e5bc75 100644
--- a/localization/i18n/tr/OrcaSlicer_tr.po
+++ b/localization/i18n/tr/OrcaSlicer_tr.po
@@ -4,7 +4,7 @@ msgstr ""
"Project-Id-Version: Orca Slicer\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2026-07-29 17:40-0300\n"
-"PO-Revision-Date: 2026-08-01 20:32+0300\n"
+"PO-Revision-Date: 2026-08-04 19:36+0300\n"
"Last-Translator: GlauTech\n"
"Language-Team: \n"
"Language: tr\n"
@@ -738,9 +738,8 @@ msgstr "Sabit adım sürükleme"
msgid "Context Menu"
msgstr "Bağlam Menüsü"
-# AI Translated
msgid "Toggle Auto-Drop"
-msgstr "Otomatik Bırakmayı Aç/Kapat"
+msgstr "Otomatik düşürmeyi aç / kapat"
msgid "Single sided scaling"
msgstr "Tek taraflı ölçekleme"
@@ -791,9 +790,8 @@ msgstr "Nesne"
msgid "Part"
msgstr "Parça"
-# AI Translated
msgid "Relative"
-msgstr "Göreli"
+msgstr "Göreceli"
# AI Translated
msgid "Coordinate system used for transform actions."
@@ -2306,7 +2304,7 @@ msgid "new or open project file is not allowed during the slicing process!"
msgstr "dilimleme işlemi sırasında yeni veya açık proje dosyasına izin verilmez!"
msgid "Open Project"
-msgstr "Projeyi Aç"
+msgstr "Projeyi aç"
msgid "The version of Orca Slicer is too low and needs to be updated to the latest version before it can be used normally."
msgstr "Orca Slicer'ın sürümü çok düşük ve normal şekilde kullanılabilmesi için en son sürüme güncellenmesi gerekiyor."
@@ -2734,16 +2732,15 @@ msgstr "Simit"
msgid "Orca Cube"
msgstr "Orca Küpü"
-# AI Translated
msgid "OrcaSliced Combo"
-msgstr "OrcaSliced Combo"
+msgstr "Orca Dilimleme Paketi"
# AI Translated
msgid "Orca Badge"
msgstr "Orca Rozeti"
msgid "Orca Tolerance Test"
-msgstr "Orca tolerans testi"
+msgstr "Orca Tolerans Testi"
msgid "3DBenchy"
msgstr "3DBenchy"
@@ -2807,7 +2804,7 @@ msgid "Set as Individual Objects"
msgstr "Bireysel nesneler olarak ayarla"
msgid "Fill bed with copies"
-msgstr "Yatağı kopyalarla doldurun"
+msgstr "Tablayı kopyalarla doldur"
msgid "Fill the remaining area of bed with copies of the selected object"
msgstr "Yatağın kalan alanını seçilen nesnenin kopyalarıyla doldurun"
@@ -2815,9 +2812,8 @@ msgstr "Yatağın kalan alanını seçilen nesnenin kopyalarıyla doldurun"
msgid "Printable"
msgstr "Yazdırılabilir"
-# AI Translated
msgid "Auto Drop"
-msgstr "Otomatik Bırakma"
+msgstr "Otomatik düşür"
# AI Translated
msgid "Automatically drops the selected object to the build plate."
@@ -2967,7 +2963,7 @@ msgid "Add Models"
msgstr "Model ekle"
msgid "Show Labels"
-msgstr "Etiketleri Göster"
+msgstr "Etiketleri göster"
msgid "To Objects"
msgstr "Nesnelere"
@@ -3009,7 +3005,7 @@ msgid "Select all objects on the current plate"
msgstr "Mevcut plakadaki tüm nesneleri seç"
msgid "Select All Plates"
-msgstr "Tüm Plakaları Seç"
+msgstr "Tüm plakaları seç"
msgid "Select all objects on all plates"
msgstr "Tüm plakalardaki tüm nesneleri seç"
@@ -3045,13 +3041,13 @@ msgid "Remove the selected plate"
msgstr "Seçilen plakayı kaldır"
msgid "Add instance"
-msgstr "Örnek ekle"
+msgstr "Kopya ekle"
msgid "Add one more instance of the selected object"
msgstr "Seçilen nesnenin bir örneğini daha ekle"
msgid "Remove instance"
-msgstr "Örneği kaldır"
+msgstr "Kopyayı kaldır"
msgid "Remove one instance of the selected object"
msgstr "Seçilen nesnenin bir örneğini kaldır"
@@ -3060,10 +3056,10 @@ msgid "Set number of instances"
msgstr "Örnek sayısını ayarlayın"
msgid "Change the number of instances of the selected object"
-msgstr "Seçilen nesnenin örnek sayısını değiştirme"
+msgstr "Seçilen nesnenin kopya sayısını değiştirme"
msgid "Fill bed with instances"
-msgstr "Yatağı örneklerle doldurun"
+msgstr "Tablayı kopyalarla doldur"
msgid "Fill the remaining area of bed with instances of the selected object"
msgstr "Yatağın kalan alanını seçilen nesnenin örnekleriyle doldurun"
@@ -3075,7 +3071,7 @@ msgid "Simplify Model"
msgstr "Modeli basitleştir"
msgid "Subdivision mesh"
-msgstr "Alt bölüm ağı"
+msgstr "Poligon artırma"
msgid "(Lost color)"
msgstr "(Renk kaybı)"
@@ -3090,10 +3086,10 @@ msgid "Edit Process Settings"
msgstr "İşlem ayarlarını düzenle"
msgid "Copy Process Settings"
-msgstr "İşlem Ayarlarını Kopyala"
+msgstr "İşlem ayarlarını kopyala"
msgid "Paste Process Settings"
-msgstr "İşlem Ayarlarını Yapıştır"
+msgstr "İşlem ayarlarını yapıştır"
msgid "Edit print parameters for a single object"
msgstr "Tek bir nesne için yazdırma parametrelerini düzenleme"
@@ -3478,7 +3474,7 @@ msgid "More"
msgstr "Daha"
msgid "Open Preferences"
-msgstr "Tercihleri Aç"
+msgstr "Tercihleri aç"
msgid "Open next tip"
msgstr "Sonraki ipucunu aç"
@@ -5450,10 +5446,10 @@ msgid "Acceleration"
msgstr "Hızlanma"
msgid "Jerk"
-msgstr "Jerk"
+msgstr "Sarsıntı"
msgid "Fan Speed"
-msgstr "Fan hızı"
+msgstr "Fan Hızı"
msgid "Flow"
msgstr "Akış"
@@ -5468,7 +5464,7 @@ msgid "Layer Time"
msgstr "Katman Süresi"
msgid "Layer Time (log)"
-msgstr "Katman Süresi (günlük)"
+msgstr "Katman Süresi (log)"
msgid "Pressure Advance"
msgstr "Basınç İlerlemesi"
@@ -5477,10 +5473,10 @@ msgid "Noop"
msgstr "Hayır"
msgid "Retract"
-msgstr "Geri Çekme"
+msgstr "Geri çekme"
msgid "Unretract"
-msgstr "İleri İtme"
+msgstr "İleri itme"
msgid "Seam"
msgstr "Dikiş"
@@ -5578,7 +5574,7 @@ msgid "Acceleration: "
msgstr "İvme: "
msgid "Jerk: "
-msgstr "Jerk: "
+msgstr "Sarsıntı: "
msgid "PA: "
msgstr "PA: "
@@ -5608,7 +5604,7 @@ msgid "Actual speed profile"
msgstr "Gerçek hız profili"
msgid "Statistics of All Plates"
-msgstr "Tüm Plakaların İstatistikleri"
+msgstr "Tüm plakaların istatistikleri"
msgid "Display"
msgstr "Ekran"
@@ -5708,7 +5704,7 @@ msgid "Acceleration (mm/s²)"
msgstr "İvme (mm/s²)"
msgid "Jerk (mm/s)"
-msgstr "Jerk (mm/s)"
+msgstr "Sarsıntı (mm/s)"
msgid "Fan speed (%)"
msgstr "Fan hızı (%)"
@@ -5759,9 +5755,8 @@ msgstr "Normal mod"
msgid "Total Filament"
msgstr "Toplam filament"
-# AI Translated
msgid "Model Filament"
-msgstr "Model Filamenti"
+msgstr "Model filamenti"
msgid "Prepare time"
msgstr "Hazırlık süresi"
@@ -6282,20 +6277,19 @@ msgid "Setup Wizard"
msgstr "Kurulum sihirbazı"
msgid "Show Configuration Folder"
-msgstr "Yapılandırma Klasörünü Göster"
+msgstr "Yapılandırma klasörünü göster"
-# AI Translated
msgid "Troubleshoot Center"
-msgstr "Sorun Giderme Merkezi"
+msgstr "Sorun giderme merkezi"
msgid "Open Network Test"
-msgstr "Ağ Testini Aç"
+msgstr "Ağ testini aç"
msgid "Show Tip of the Day"
-msgstr "Günün İpucunu Göster"
+msgstr "Günün ipucunu göster"
msgid "Check for Updates"
-msgstr "Güncellemeleri Kontrol Et"
+msgstr "Güncellemeleri kontrol et"
#, c-format, boost-format
msgid "&About %s"
@@ -6349,7 +6343,7 @@ msgid "Recent files"
msgstr "Son dosyalar"
msgid "Save Project"
-msgstr "Projeyi Kaydet"
+msgstr "Projeyi kaydet"
msgid "Save current project to file"
msgstr "Mevcut projeyi dosyaya kaydet"
@@ -6415,13 +6409,13 @@ msgid "Export toolpaths as OBJ"
msgstr "Takımyollarını OBJ olarak dışa aktar"
msgid "Export Preset Bundle"
-msgstr "Ön Ayar Paketini Dışa Aktar"
+msgstr "Ön ayar paketini dışa aktar"
msgid "Export current configuration to files"
msgstr "Geçerli yapılandırmayı dosyalara aktar"
msgid "Export"
-msgstr "Dışa Aktar"
+msgstr "Dışa aktar"
msgid "Quit"
msgstr "Çıkış"
@@ -6478,13 +6472,13 @@ msgid "Deselects all objects"
msgstr "Tüm nesnelerin seçimini kaldırır"
msgid "Use Perspective View"
-msgstr "Perspektif Görünüm"
+msgstr "Perspektif görünüm"
msgid "Use Orthogonal View"
-msgstr "Ortogonal Görünüm"
+msgstr "Ortogonal görünüm"
msgid "Auto Perspective"
-msgstr "Otomatik Perspektif"
+msgstr "Otomatik perspektif"
msgid "Automatically switch between orthographic and perspective when changing from top/bottom/side views."
msgstr "Üst/Alt/Yan görünümler arasında geçiş yaparken ortografik ve perspektif arasında otomatik olarak geçiş yapın."
@@ -6496,37 +6490,37 @@ msgid "Show G-code window in Preview scene."
msgstr "Previce sahnesinde G-kodu penceresini göster."
msgid "Show 3D Navigator"
-msgstr "3D Gezgini Göster"
+msgstr "3D gezgini göster"
msgid "Show 3D navigator in Prepare and Preview scene."
msgstr "Hazırlama ve Önizleme sahnesinde 3D gezgini göster."
msgid "Show Gridlines"
-msgstr "Kılavuz Çizgilerini Göster"
+msgstr "Kılavuz çizgilerini göster"
msgid "Show Gridlines on plate"
msgstr "Kılavuz Çizgilerini plaka üzerinde göster"
msgid "Reset Window Layout"
-msgstr "Pencere Düzenini Sıfırla"
+msgstr "Pencere düzenini sıfırla"
msgid "Reset to default window layout"
msgstr "Varsayılan pencere düzenine sıfırla"
msgid "Show &Labels"
-msgstr "Etiketleri Göster"
+msgstr "Etiketleri göster"
msgid "Show object labels in 3D scene."
msgstr "3B sahnede nesne etiketlerini göster."
msgid "Show &Overhang"
-msgstr "Çıkıntıyı Göster"
+msgstr "Çıkıntıyı göster"
msgid "Show object overhang highlight in 3D scene."
msgstr "3B sahnede nesne çıkıntısı vurgusunu göster."
msgid "Show Selected Outline (beta)"
-msgstr "Seçilen Taslağı Göster (Deneysel)"
+msgstr "Seçilen taslağı göster (deneysel)"
msgid "Show outline around selected object in 3D scene."
msgstr "3D sahnede seçilen nesnenin etrafındaki ana hatları göster."
@@ -6542,13 +6536,11 @@ msgstr "Düzen"
msgid "View"
msgstr "Görünüm"
-# AI Translated
msgid "Preset Bundle"
-msgstr "Ön Ayar Paketi"
+msgstr "Ön ayar paketi"
-# AI Translated
msgid "Sync Presets"
-msgstr "Ön Ayarları Eşitle"
+msgstr "Ön ayarları eşitle"
# AI Translated
msgid "Pull and apply the latest presets from OrcaCloud"
@@ -6590,10 +6582,10 @@ msgid "Cornering calibration"
msgstr "Viraj kalibrasyonu"
msgid "Input Shaping Frequency"
-msgstr "Input shaping Frekansı"
+msgstr "Input shaping frekansı"
msgid "Input Shaping Damping/zeta factor"
-msgstr "Input shaping Sönümleme/zeta faktörü"
+msgstr "Input shaping sönümleme/zeta faktörü"
msgid "Input Shaping"
msgstr "Input shaping"
@@ -6601,9 +6593,8 @@ msgstr "Input shaping"
msgid "VFA"
msgstr "VFA"
-# AI Translated
msgid "Calibration Guide"
-msgstr "Kalibrasyon Kılavuzu"
+msgstr "Kalibrasyon kılavuzu"
msgid "&Open G-code"
msgstr "&G kodunu aç"
@@ -8219,9 +8210,8 @@ msgstr "Bu dosyalar birden fazla parçadan oluşan tek bir nesne olarak mı yük
msgid "An object with multiple parts was detected"
msgstr "Birden fazla parçaya sahip nesne algılandı"
-# AI Translated
msgid "Auto-Drop"
-msgstr "Otomatik Bırakma"
+msgstr "Otomatik düşür"
#, c-format, boost-format
msgid "Connected printer is %s. It must match the project preset for printing.\n"
@@ -8296,9 +8286,8 @@ msgstr "Seçilen nesne bölünemedi."
msgid "Split to Objects"
msgstr "Nesnelere Ayır"
-# AI Translated
msgid "Disable Auto-Drop to preserve Z positioning?\n"
-msgstr "Z konumunu korumak için Otomatik Bırakma devre dışı bırakılsın mı?\n"
+msgstr "Z konumunu korumak için Otomatik düşürme devre dışı bırakılsın mı?\n"
# AI Translated
msgid "Object with floating parts was detected"
@@ -8433,7 +8422,7 @@ msgid "Creating a new project"
msgstr "Yeni bir proje oluşturma"
msgid "Load project"
-msgstr "Projeyi Aç"
+msgstr "Projeyi aç"
msgid ""
"Failed to save the project.\n"
@@ -8869,10 +8858,10 @@ msgid "Current Association: "
msgstr "Mevcut Bağlantı: "
msgid "Current Instance"
-msgstr "Mevcut Örnek"
+msgstr "Mevcut Kopya"
msgid "Current Instance Path: "
-msgstr "Mevcut Örnek Yolu: "
+msgstr "Mevcut Kopya Yolu: "
msgid "General"
msgstr "Genel"
@@ -10708,7 +10697,7 @@ msgid "Reserved keywords found"
msgstr "Ayrılmış anahtar kelimeler bulundu"
msgid "Setting Overrides"
-msgstr "Ayarların Üzerine Yazma"
+msgstr "Ayarların Üzerine Yaz"
msgid "Basic information"
msgstr "Temel Bilgiler"
@@ -12227,7 +12216,7 @@ msgid "Group error in manual mode. Please check nozzle count or regroup."
msgstr "Elle modda gruplama hatası. Lütfen nozul sayısını denetleyin veya yeniden gruplayın."
msgid "Internal Bridge"
-msgstr "İç Köprü"
+msgstr "İç köprü"
msgid "undefined error"
msgstr "bilinmeyen hata"
@@ -12920,7 +12909,6 @@ msgstr "Çıkıntı bu belirtilen eşiği aştığında, soğutma fanını aşa
msgid "External bridge infill direction"
msgstr "Dış köprü dolgu yönü"
-# AI Translated
#, no-c-format, no-boost-format
msgid ""
"External Bridging angle override.\n"
@@ -12937,14 +12925,13 @@ msgstr ""
"Aksi hâlde verilen açı şuna göre kullanılır:\n"
" - Mutlak koordinatlar\n"
" - Mutlak koordinatlar + Model dönüşü: Yönleri modele hizala etkinse\n"
-" - En uygun otomatik açı + bu değer: 'Göreli Köprü Açısı' etkinse\n"
+" - En uygun otomatik açı + bu değer: ‘Göreceli Köprü Açısı' etkinse\n"
"\n"
"Sıfır mutlak açı için 180° kullanın."
msgid "Internal bridge infill direction"
msgstr "İç köprü dolgu yönü"
-# AI Translated
msgid ""
"Internal Bridging angle override.\n"
"If left to zero, the bridging angle will be calculated automatically for each specific bridge.\n"
@@ -12960,13 +12947,12 @@ msgstr ""
"Aksi hâlde verilen açı şuna göre kullanılır:\n"
" - Mutlak koordinatlar\n"
" - Mutlak koordinatlar + Model dönüşü: Yönleri modele hizala etkinse\n"
-" - En uygun otomatik açı + bu değer: 'Göreli Köprü Açısı' etkinse\n"
+" - En uygun otomatik açı + bu değer: 'Göreceli Köprü Açısı' etkinse\n"
"\n"
"Sıfır mutlak açı için 180° kullanın."
-# AI Translated
msgid "Relative bridge angle"
-msgstr "Göreli köprü açısı"
+msgstr "Göreceli köprü açısı"
# AI Translated
msgid "When enabled, the bridge angle values are added to the automatically calculated bridge direction instead of overriding it."
@@ -13413,7 +13399,7 @@ msgstr ""
"Not: Elde edilen değer ilk katman akış oranından etkilenmez."
msgid "Brim follows compensated outline"
-msgstr "Kenar telafi edilen taslağı takip ediyor"
+msgstr "Kenar toleranslı dış sınırı takip etsin"
# AI Translated
msgid ""
@@ -13724,13 +13710,13 @@ msgid ""
msgstr ""
"Dikey kabuk kalınlığını garanti etmek için eğimli yüzeylerin yakınına katı dolgu ekleyin (üst + alt katı katmanlar)\n"
"Yok: Hiçbir yere katı dolgu eklenmez. Dikkat: Modelinizin eğimli yüzeyleri varsa bu seçeneği dikkatli kullanın.\n"
-"Yalnızca kritik: Duvarlar için katı dolgu eklemekten kaçının\n"
+"Kritik: Duvarlar için katı dolgu eklemekten kaçının\n"
"Orta: Yalnızca çok eğimli yüzeyler için katı dolgu ekleyin\n"
"Hepsi: Tüm uygun eğimli yüzeyler için katı dolgu ekleyin\n"
"Varsayılan değer Tümü'dür."
msgid "Critical Only"
-msgstr "Yalnızca kritik"
+msgstr "Kritik"
msgid "Moderate"
msgstr "Orta"
@@ -14247,7 +14233,7 @@ msgid "By First filament"
msgstr "İlk filamente göre"
msgid "By Highest Temp"
-msgstr "En Yüksek Sıcaklığa Göre"
+msgstr "En yüksek sıcaklığa göre"
msgid "Filament diameter is used to calculate extrusion variables in G-code, so it is important that this is accurate and precise."
msgstr "Filament çapı, gcode'da ekstrüzyonu hesaplamak için kullanılır; bu nedenle önemlidir ve doğru olmalıdır."
@@ -14685,10 +14671,10 @@ msgid "Marlin Firmware Junction Deviation (replaces the traditional XY Jerk sett
msgstr "Marlin Firmware Köşe Sapması (geleneksel XY Sarsıntı ayarının yerini alır)"
msgid "Jerk of outer walls."
-msgstr "Dış duvar JERK değeri."
+msgstr "Dış duvar sarsıntı değeri."
msgid "Jerk of inner walls."
-msgstr "İç duvarlar JERK değeri."
+msgstr "İç duvarlar sarsıntı değeri."
msgid "Jerk for top surface."
msgstr "Üst yüzey için JERK değeri."
@@ -15721,7 +15707,7 @@ msgid ""
"If your Marlin 2 printer uses Classic Jerk set this value to 0.)"
msgstr ""
"Maksimum bağlantı sapması (M205 J, yalnızca Marlin Aygıt Yazılımı için JD > 0 ise geçerlidir)\n"
-"Marlin 2 yazıcınız Classic Jerk kullanıyorsa bu değeri 0 olarak ayarlayın.)"
+"Marlin 2 yazıcınız Classic sarsıntı kullanıyorsa bu değeri 0 olarak ayarlayın.)"
msgid "Minimum speed for extruding"
msgstr "Ekstrüzyon için minimum hız"
@@ -16914,7 +16900,7 @@ msgid "This setting only generates supports that begin on the build plate."
msgstr "Model yüzeyinde destek oluşturmayın, yalnızca baskı plakasında."
msgid "Support critical regions only"
-msgstr "Yalnızca kritik bölgeleri destekleyin"
+msgstr "Kritik bölgeleri destekleyin"
msgid "Only create support for critical regions including sharp tail, cantilever, etc."
msgstr "Yalnızca keskin kuyruk, konsol vb. gibi kritik bölgeler için destek oluşturun."
@@ -19288,13 +19274,13 @@ msgid ""
"To test Classic Jerk, set 'Maximum Junction Deviation' in Motion ability to 0."
msgstr ""
"Marlin 2 Kavşak Sapması tespit edildi:\n"
-"Classic Jerk'i test etmek için Hareket yeteneğinde 'Maksimum Kavşak Sapması'nı 0'a ayarlayın."
+"Classic sarsıntıyı test etmek için Hareket yeteneğinde 'Maksimum Kavşak Sapması'nı 0'a ayarlayın."
msgid ""
"Marlin 2 Classic Jerk detected:\n"
"To test Junction Deviation, set 'Maximum Junction Deviation' in Motion ability to a value > 0."
msgstr ""
-"Marlin 2 Classic Jerk tespit edildi:\n"
+"Marlin 2 Classic sarsıntı tespit edildi:\n"
"Kavşak Sapmasını test etmek için Hareket yeteneğinde 'Maksimum Kavşak Sapması'nı > 0 değerine ayarlayın."
msgid ""
@@ -19538,7 +19524,7 @@ msgid "Start Test Single-Thread"
msgstr "Tek İş Parçacığı Testini Başlat"
msgid "Export Log"
-msgstr "Logu Dışa Aktar"
+msgstr "Logu dışa aktar"
msgid "OrcaSlicer Version:"
msgstr "OrcaSlicer Sürümü:"
@@ -20281,9 +20267,8 @@ msgstr "Sistem klasörü silinemedi..."
msgid "Failed to determine executable path."
msgstr "Yürütülebilir dosya yolu belirlenemedi."
-# AI Translated
msgid "Failed to launch a new instance."
-msgstr "Yeni bir örnek başlatılamadı."
+msgstr "Yeni bir kopya başlatılamadı."
# AI Translated
msgid "log(s)"
diff --git a/resources/profiles/Snapmaker.json b/resources/profiles/Snapmaker.json
index ab2433242e..9a6fab7942 100644
--- a/resources/profiles/Snapmaker.json
+++ b/resources/profiles/Snapmaker.json
@@ -1,6 +1,6 @@
{
"name": "Snapmaker",
- "version": "02.04.00.08",
+ "version": "02.04.00.09",
"force_update": "0",
"description": "Snapmaker configurations",
"machine_model_list": [
diff --git a/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.2 nozzle).json b/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.2 nozzle).json
index aebc032855..183e125c73 100644
--- a/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.2 nozzle).json
+++ b/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.2 nozzle).json
@@ -186,7 +186,6 @@
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\nTIMELAPSE_TAKE_FRAME\nDEFECT_DETECTION_DETECT",
"machine_pause_gcode": "M600",
"nozzle_volume": "143",
- "support_multi_bed_types": "0",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nSET_PRINT_STATS_INFO TOTAL_LAYER={total_layer_count} CURRENT_LAYER={layer_num+1}",
"default_print_profile": "0.10 Standard @Snapmaker U1 (0.2 nozzle)"
}
diff --git a/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.4 nozzle).json b/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.4 nozzle).json
index 28ccfd0a29..6d2ec2cfe6 100644
--- a/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.4 nozzle).json
+++ b/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.4 nozzle).json
@@ -186,7 +186,6 @@
"before_layer_change_gcode": ";BEFORE_LAYER_CHANGE\n;[layer_z]\nG92 E0\nTIMELAPSE_TAKE_FRAME\nDEFECT_DETECTION_DETECT",
"default_print_profile": "0.20 Standard @Snapmaker U1 (0.4 nozzle)",
"machine_pause_gcode": "M600",
- "default_bed_type": "Textured PEI Plate",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nSET_PRINT_STATS_INFO TOTAL_LAYER={total_layer_count} CURRENT_LAYER={layer_num+1}",
"nozzle_volume": "143",
"resonance_avoidance": "1",
diff --git a/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.6 nozzle).json b/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.6 nozzle).json
index f4dff2f357..a6cb0d0bd3 100644
--- a/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.6 nozzle).json
+++ b/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.6 nozzle).json
@@ -187,6 +187,5 @@
"machine_pause_gcode": "M600",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nSET_PRINT_STATS_INFO TOTAL_LAYER={total_layer_count} CURRENT_LAYER={layer_num+1}",
"nozzle_volume": "143",
- "support_multi_bed_types": "0",
"default_print_profile": "0.30 Standard @Snapmaker U1 (0.6 nozzle)"
}
diff --git a/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.8 nozzle).json b/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.8 nozzle).json
index e356f4264b..ef4da1a516 100644
--- a/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.8 nozzle).json
+++ b/resources/profiles/Snapmaker/machine/Snapmaker U1 (0.8 nozzle).json
@@ -187,6 +187,5 @@
"machine_pause_gcode": "M600",
"layer_change_gcode": ";AFTER_LAYER_CHANGE\n;[layer_z]\nSET_PRINT_STATS_INFO TOTAL_LAYER={total_layer_count} CURRENT_LAYER={layer_num+1}",
"nozzle_volume": "143",
- "support_multi_bed_types": "0",
"default_print_profile": "0.40 Standard @Snapmaker U1 (0.8 nozzle)"
}
diff --git a/resources/profiles/Snapmaker/machine/fdm_U1.json b/resources/profiles/Snapmaker/machine/fdm_U1.json
index 7ee65878e6..717215b507 100644
--- a/resources/profiles/Snapmaker/machine/fdm_U1.json
+++ b/resources/profiles/Snapmaker/machine/fdm_U1.json
@@ -183,7 +183,8 @@
"scan_first_layer": "0",
"nozzle_type": "undefine",
"auxiliary_fan": "0",
- "default_bed_type": "Textured PEI Plate",
+ "support_multi_bed_types": "1",
+ "default_bed_type": "4",
"printable_area": [
"0.5x1",
"270.5x1",
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 79b49cfd16..0082b29831 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -256,14 +256,6 @@ if (WIN32)
VERBATIM
)
endforeach ()
-
- if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
- orcaslicer_copy_dlls(COPY_DLLS "Debug" "d" output_dlls_Debug)
- elseif("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
- orcaslicer_copy_dlls(COPY_DLLS "RelWithDebInfo" "" output_dlls_Release)
- else()
- orcaslicer_copy_dlls(COPY_DLLS "Release" "" output_dlls_Release)
- endif()
else ()
file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/resources" WIN_RESOURCES_SYMLINK)
add_custom_command(TARGET OrcaSlicer POST_BUILD
@@ -279,6 +271,27 @@ if (WIN32)
COMMENT "Copying Python runtime into the build tree"
VERBATIM)
+ if (CMAKE_CONFIGURATION_TYPES)
+ # Multi-config generators (Visual Studio, Ninja Multi-Config): copy per config.
+ foreach (cfg ${CMAKE_CONFIGURATION_TYPES})
+ if ("${cfg}" STREQUAL "Debug")
+ orcaslicer_copy_dlls(COPY_DLLS "Debug" "d" output_dlls_Debug)
+ elseif("${cfg}" STREQUAL "RelWithDebInfo")
+ orcaslicer_copy_dlls(COPY_DLLS "RelWithDebInfo" "" output_dlls_RelWithDebInfo)
+ else()
+ orcaslicer_copy_dlls(COPY_DLLS "${cfg}" "" output_dlls_${cfg})
+ endif()
+ endforeach()
+ else()
+ # Single-config generators (Ninja): use CMAKE_BUILD_TYPE.
+ if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug")
+ orcaslicer_copy_dlls(COPY_DLLS "Debug" "d" output_dlls_Debug)
+ elseif("${CMAKE_BUILD_TYPE}" STREQUAL "RelWithDebInfo")
+ orcaslicer_copy_dlls(COPY_DLLS "RelWithDebInfo" "" output_dlls_RelWithDebInfo)
+ else()
+ orcaslicer_copy_dlls(COPY_DLLS "Release" "" output_dlls_Release)
+ endif()
+ endif()
else ()
if (APPLE AND NOT CMAKE_MACOSX_BUNDLE)
diff --git a/src/dev-utils/BaseException.cpp b/src/dev-utils/BaseException.cpp
index f33c8f635f..efb7a98245 100644
--- a/src/dev-utils/BaseException.cpp
+++ b/src/dev-utils/BaseException.cpp
@@ -69,7 +69,7 @@ void CBaseException::OutputString(LPCTSTR lpszFormat, ...)
//WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), szBuf, _tcslen(szBuf), NULL, NULL);
//output it to the current directory of binary
- std::string output_str = textconv_helper::T2A_(szBuf);
+ std::string output_str = static_cast(textconv_helper::T2A_(szBuf));
*output_file << output_str;
output_file->flush();
}
diff --git a/src/libslic3r/AppConfig.cpp b/src/libslic3r/AppConfig.cpp
index d843d51891..e74a02ca32 100644
--- a/src/libslic3r/AppConfig.cpp
+++ b/src/libslic3r/AppConfig.cpp
@@ -289,6 +289,9 @@ void AppConfig::set_defaults()
set(SETTING_OPENGL_FPS_CAP, std::to_string(fps_cap));
}
+ // The getter already defaults, parses and clamps; write back what it resolves to.
+ set(SETTING_PLUGIN_PAGES_VISIBLE_COUNT, std::to_string(get_plugin_pages_visible_count()));
+
if (get(SETTING_OPENGL_SHOW_FPS_OVERLAY).empty())
set_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY, false);
@@ -1646,6 +1649,22 @@ void AppConfig::set_network_plugin_version(const std::string& version)
set(SETTING_NETWORK_PLUGIN_VERSION, version);
}
+int AppConfig::get_plugin_pages_visible_count() const
+{
+ std::string value = get(SETTING_PLUGIN_PAGES_VISIBLE_COUNT);
+ if (value.empty())
+ return PLUGIN_PAGES_VISIBLE_COUNT_DEFAULT;
+
+ int visible_count = PLUGIN_PAGES_VISIBLE_COUNT_DEFAULT;
+ try {
+ visible_count = std::stoi(value);
+ }
+ catch (...) {
+ return PLUGIN_PAGES_VISIBLE_COUNT_DEFAULT;
+ }
+ return std::clamp(visible_count, PLUGIN_PAGES_VISIBLE_COUNT_MIN, PLUGIN_PAGES_VISIBLE_COUNT_MAX);
+}
+
std::vector AppConfig::get_skipped_network_versions() const
{
std::vector result;
diff --git a/src/libslic3r/AppConfig.hpp b/src/libslic3r/AppConfig.hpp
index 9ec0cf0fc9..1c1a676a82 100644
--- a/src/libslic3r/AppConfig.hpp
+++ b/src/libslic3r/AppConfig.hpp
@@ -41,6 +41,11 @@ using namespace nlohmann;
#define SETTING_OPENGL_PHONG_SSAO "opengl_phong_ssao"
#define SETTING_OPENGL_PHONG_SMOOTH_NORMALS "opengl_phong_smooth_normals"
+#define SETTING_PLUGIN_PAGES_VISIBLE_COUNT "plugin_pages_visible_count"
+#define PLUGIN_PAGES_VISIBLE_COUNT_MIN 1
+#define PLUGIN_PAGES_VISIBLE_COUNT_DEFAULT 5
+#define PLUGIN_PAGES_VISIBLE_COUNT_MAX 10
+
#if defined(_WIN32) || defined(_WIN64)
#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.09"
#else
@@ -381,6 +386,10 @@ public:
std::string get_network_plugin_version() const;
void set_network_plugin_version(const std::string& version);
+ // Number of plugin pages shown as fixed tabs before the rest are collapsed into a
+ // dropdown on the last tab.
+ int get_plugin_pages_visible_count() const;
+
std::vector get_skipped_network_versions() const;
void add_skipped_network_version(const std::string& version);
bool is_network_version_skipped(const std::string& version) const;
diff --git a/src/libslic3r/BoundingBox.cpp b/src/libslic3r/BoundingBox.cpp
index a2a510b64c..cf5441dace 100644
--- a/src/libslic3r/BoundingBox.cpp
+++ b/src/libslic3r/BoundingBox.cpp
@@ -8,6 +8,8 @@
namespace Slic3r {
template BoundingBoxBase::BoundingBoxBase(const Points &points);
+template void BoundingBoxBase::construct<0, BoundingBox, Points::const_iterator>(BoundingBox&, Points::const_iterator, Points::const_iterator);
+template void BoundingBoxBase::construct<1, BoundingBox, Points::const_iterator>(BoundingBox&, Points::const_iterator, Points::const_iterator);
template BoundingBoxBase::BoundingBoxBase(const std::vector &points);
template BoundingBox3Base::BoundingBox3Base(const std::vector &points);
diff --git a/src/libslic3r/CMakeLists.txt b/src/libslic3r/CMakeLists.txt
index 7241002295..767fb1ac74 100644
--- a/src/libslic3r/CMakeLists.txt
+++ b/src/libslic3r/CMakeLists.txt
@@ -154,6 +154,8 @@ set(lisbslic3r_sources
Fill/FillConcentric.hpp
Fill/FillConcentricInternal.cpp
Fill/FillConcentricInternal.hpp
+ Fill/FillCornerSmoothing.cpp
+ Fill/FillCornerSmoothing.hpp
Fill/Fill.cpp
Fill/FillCrossHatch.cpp
Fill/FillCrossHatch.hpp
diff --git a/src/libslic3r/Config.hpp b/src/libslic3r/Config.hpp
index 509095cbfc..ef93f0d509 100644
--- a/src/libslic3r/Config.hpp
+++ b/src/libslic3r/Config.hpp
@@ -2982,6 +2982,8 @@ public:
const double & opt_float(const t_config_option_key &opt_key, unsigned int idx) const;
double & opt_float_nullable(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); }
const double & opt_float_nullable(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); }
+ FloatOrPercent & opt_float_or_percent_nullable(const t_config_option_key &opt_key, unsigned int idx) { return this->option(opt_key)->get_at(idx); }
+ const FloatOrPercent & opt_float_or_percent_nullable(const t_config_option_key &opt_key, unsigned int idx) const { return dynamic_cast(this->option(opt_key))->get_at(idx); }
int& opt_int(const t_config_option_key &opt_key) { return this->option(opt_key)->value; }
int opt_int(const t_config_option_key &opt_key) const { return dynamic_cast(this->option(opt_key))->value; }
diff --git a/src/libslic3r/Fill/Fill.cpp b/src/libslic3r/Fill/Fill.cpp
index 88d87ddb26..0888e1bb55 100644
--- a/src/libslic3r/Fill/Fill.cpp
+++ b/src/libslic3r/Fill/Fill.cpp
@@ -970,9 +970,9 @@ std::vector group_fills(const Layer &layer, LockRegionParam &lock_p
region_config.sparse_infill_rotate_template.value);
params.fixed_angle = !region_config.sparse_infill_rotate_template.value.empty();
- // Orca: special case; apply smoothing factor only for Hilbert Curve sparse infill.
- // FillHilbertCurve::generate clamps and validates the value itself.
- if (params.pattern == ipHilbertCurve)
+ // Orca: the smoothing factor only applies to the sparse infill patterns that
+ // implement it. The fills clamp and validate the value themselves.
+ if (is_smoothable_infill_pattern(params.pattern, params.multiline))
params.smooth_factor = 0.01 * region_config.sparse_infill_smooth_factor.value;
} else {
const bool top_layer_direction_set = surface.is_top() && region_config.top_layer_direction.value >= 0.;
diff --git a/src/libslic3r/Fill/Fill3DHoneycomb.cpp b/src/libslic3r/Fill/Fill3DHoneycomb.cpp
index 5908f854de..ad5f8918fd 100644
--- a/src/libslic3r/Fill/Fill3DHoneycomb.cpp
+++ b/src/libslic3r/Fill/Fill3DHoneycomb.cpp
@@ -2,6 +2,7 @@
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
#include "FillBase.hpp"
+#include "FillCornerSmoothing.hpp"
#include "Fill3DHoneycomb.hpp"
namespace Slic3r {
@@ -271,6 +272,9 @@ void Fill3DHoneycomb::_fill_surface_single(
for (Polyline &pl : polylines){
pl.translate(bb.min);
pl.simplify(5 * spacing); // simplify to 5x line width
+ // Orca: round the corners of the octahedral wave. The layers where the wave degenerates to a
+ // straight line have no corner to round.
+ smooth_polyline_corners(pl, params.smooth_factor, scaled(params.resolution));
}
// Apply multiline offset if needed
diff --git a/src/libslic3r/Fill/FillConcentric.cpp b/src/libslic3r/Fill/FillConcentric.cpp
index a75d2ed7d3..1882f7a656 100644
--- a/src/libslic3r/Fill/FillConcentric.cpp
+++ b/src/libslic3r/Fill/FillConcentric.cpp
@@ -5,6 +5,7 @@
#include "Arachne/WallToolPaths.hpp"
#include "FillConcentric.hpp"
+#include "FillCornerSmoothing.hpp"
#include
namespace Slic3r {
@@ -32,12 +33,32 @@ void FillConcentric::_fill_surface_single(
Polygons loops = to_polygons(contracted);
- ExPolygons last { std::move(contracted) };
+ ExPolygons last { contracted };
while (! last.empty()) {
last = offset2_ex(last, -(distance + min_spacing/2), +min_spacing/2);
append(loops, to_polygons(last));
}
+ // Orca: round the corners of the loops. Unlike the other patterns these are never clipped to the
+ // fill region - they are its offsets - so a corner may only be rounded where the curve replacing it
+ // stays inside. Rounding cuts toward the inside of the turn, which around a hole, at a concave
+ // feature or across a thin region is outside the fill and would put the extrusion over a wall.
+ // The reach is capped at half the distance between two loops as well: a loop is as long as the
+ // object, and a corner cut by half of its side would swallow the neighbouring loops.
+ auto corner_stays_inside = [&contracted](const Vec2d &from, const Vec2d &to) {
+ // The straight chord between the ends of the curve is the deepest the curve can cut.
+ for (const double t : { 0.25, 0.5, 0.75 }) {
+ const Vec2d sample = from + t * (to - from);
+ const Point point(coord_t(sample.x()), coord_t(sample.y()));
+ if (std::none_of(contracted.begin(), contracted.end(),
+ [&point](const ExPolygon ®ion) { return region.contains(point); }))
+ return false;
+ }
+ return true;
+ };
+ smooth_polygons_corners(loops, params.smooth_factor, scaled(params.resolution), 0.5 * distance,
+ corner_stays_inside);
+
// generate paths from the outermost to the innermost, to avoid
// adhesion problems of the first central tiny loops
loops = union_pt_chained_outside_in(loops);
diff --git a/src/libslic3r/Fill/FillCornerSmoothing.cpp b/src/libslic3r/Fill/FillCornerSmoothing.cpp
new file mode 100644
index 0000000000..2af9f6bb9c
--- /dev/null
+++ b/src/libslic3r/Fill/FillCornerSmoothing.cpp
@@ -0,0 +1,226 @@
+#include
+
+#include "FillCornerSmoothing.hpp"
+
+namespace Slic3r {
+
+// Turns sharper than this are left untouched: both ends of the curve replacing such a corner nearly
+// coincide, so the corner would be rounded into a degenerate loop instead of a hairpin.
+static constexpr const double min_smoothed_turn_cosine = -0.9;
+
+// The control points are expressed in the (incoming, outgoing) basis of the corner, which is not
+// orthonormal for turns other than a right angle.
+using QuinticBezier = std::array;
+
+static bool is_bezier_flat(const QuinticBezier &curve, const Vec2d &incoming, const Vec2d &outgoing, const double deviation)
+{
+ // A Bezier curve stays inside the convex hull of its control points. Therefore, keeping every
+ // control point within a deviation-wide strip around the endpoint chord conservatively bounds the
+ // flattening error. The cross product is the perpendicular distance scaled by the chord length;
+ // comparing squared values avoids a square root.
+ auto in_plane = [&incoming, &outgoing](const Vec2d &c) { return c.x() * incoming + c.y() * outgoing; };
+ const Vec2d chord = in_plane(curve.back() - curve.front());
+ const double chord_length_sq = chord.squaredNorm();
+ const double max_cross_sq = deviation * deviation * chord_length_sq;
+
+ for (size_t i = 1; i + 1 < curve.size(); ++i) {
+ const Vec2d offset = in_plane(curve[i] - curve.front());
+ const double cross = chord.x() * offset.y() - chord.y() * offset.x();
+ if (cross * cross > max_cross_sq)
+ return false;
+ }
+ return true;
+}
+
+static void subdivide_bezier(const QuinticBezier &curve, QuinticBezier &left, QuinticBezier &right)
+{
+ // Split the curve at t = 0.5 using de Casteljau's algorithm. Each averaging level contributes one
+ // control point to the left half and one to the right half; the latter is filled backwards to keep
+ // both resulting control polygons in their original parameter direction.
+ QuinticBezier subdivision = curve;
+ left.front() = subdivision.front();
+ right.back() = subdivision.back();
+ for (size_t level = 1; level < curve.size(); ++level) {
+ for (size_t i = 0; i + level < curve.size(); ++i)
+ subdivision[i] = 0.5 * (subdivision[i] + subdivision[i + 1]);
+ left[level] = subdivision.front();
+ right[curve.size() - level - 1] = subdivision[curve.size() - level - 1];
+ }
+}
+
+static void flatten_bezier(
+ const QuinticBezier &curve, const Vec2d &incoming, const Vec2d &outgoing, const double deviation, std::vector &output)
+{
+ // Subdivide to at least depth 1 so a rounded corner cannot collapse to a single diagonal chord.
+ // A uniform subdivision depth keeps samples at equal parameter intervals t = k / 2^depth,
+ // avoiding abrupt segment-length jumps at adaptive-depth boundaries.
+ static constexpr size_t max_depth = 16;
+
+ std::vector subcurves(2);
+ subdivide_bezier(curve, subcurves[0], subcurves[1]);
+
+ for (size_t depth = 1; depth < max_depth; ++depth) {
+ bool all_flat = true;
+ for (const QuinticBezier &c : subcurves)
+ if (!is_bezier_flat(c, incoming, outgoing, deviation)) {
+ all_flat = false;
+ break;
+ }
+ if (all_flat)
+ break;
+ std::vector finer(subcurves.size() * 2);
+ for (size_t i = 0; i < subcurves.size(); ++i)
+ subdivide_bezier(subcurves[i], finer[i * 2], finer[i * 2 + 1]);
+ subcurves = std::move(finer);
+ }
+
+ // The curve start is deliberately omitted so it can be shared with the straight leg feeding into it.
+ output.clear();
+ output.reserve(subcurves.size());
+ for (const QuinticBezier &c : subcurves)
+ output.emplace_back(c.back());
+}
+
+const std::vector& CornerSmoother::curve_coefficients(
+ const double corner_distance, const Vec2d &incoming, const Vec2d &outgoing)
+{
+ const double cosine = incoming.dot(outgoing);
+ // Corners of the same size and turn angle are congruent, so they flatten identically. An infill
+ // path walks over the very same corner over and over again, the Hilbert curve over a single one.
+ if (m_has_cached_coefficients && corner_distance == m_cached_distance && cosine == m_cached_cosine)
+ return m_cached_coefficients;
+
+ // One canonical corner running from -corner_distance along the incoming leg to corner_distance
+ // along the outgoing one. At each end, the first three control points are collinear and equally
+ // spaced: the tangent follows the adjoining straight leg and the second derivative is zero. The
+ // endpoint curvature is therefore zero, giving G2 joins to both legs.
+ const double d = corner_distance;
+ const QuinticBezier corner_curve {{
+ {-d, 0.}, {-0.7 * d, 0.}, {-0.4 * d, 0.}, {0., 0.4 * d}, {0., 0.7 * d}, {0., d}
+ }};
+ // Retain a finite positive tolerance if the smoother was set up with an invalid one.
+ const double deviation = m_tolerance > 0. && std::isfinite(m_tolerance) ? m_tolerance : EPSILON;
+ flatten_bezier(corner_curve, incoming, outgoing, deviation, m_cached_coefficients);
+
+ m_cached_distance = corner_distance;
+ m_cached_cosine = cosine;
+ m_has_cached_coefficients = true;
+ return m_cached_coefficients;
+}
+
+void CornerSmoother::round_corner(const Vec2d &previous, const Vec2d &corner, const Vec2d &next)
+{
+ m_corner_points.clear();
+
+ const Vec2d incoming_leg = corner - previous;
+ const Vec2d outgoing_leg = next - corner;
+ const double incoming_length = incoming_leg.norm();
+ const double outgoing_length = outgoing_leg.norm();
+ if (incoming_length < EPSILON || outgoing_length < EPSILON) {
+ m_corner_points.emplace_back(corner);
+ return;
+ }
+
+ const Vec2d incoming = incoming_leg / incoming_length;
+ const Vec2d outgoing = outgoing_leg / outgoing_length;
+ const double cross = incoming.x() * outgoing.y() - incoming.y() * outgoing.x();
+ // A collinear vertex is no corner at all, and a hairpin cannot be rounded, see above.
+ if (std::abs(cross) < EPSILON || incoming.dot(outgoing) < min_smoothed_turn_cosine) {
+ m_corner_points.emplace_back(corner);
+ return;
+ }
+
+ // Consuming at most half of the shorter leg keeps the curves of two adjacent corners apart.
+ double corner_distance = m_corner_distance_ratio * std::min(incoming_length, outgoing_length);
+ if (m_max_corner_distance > 0.)
+ corner_distance = std::min(corner_distance, m_max_corner_distance);
+
+ const Vec2d curve_start = corner - corner_distance * incoming;
+ const Vec2d curve_end = corner + corner_distance * outgoing;
+ if (m_corner_filter && !m_corner_filter(curve_start, curve_end)) {
+ m_corner_points.emplace_back(corner);
+ return;
+ }
+
+ const std::vector &coefficients = curve_coefficients(corner_distance, incoming, outgoing);
+ m_corner_points.reserve(coefficients.size() + 1);
+ m_corner_points.emplace_back(curve_start);
+ for (const Vec2d &coefficient : coefficients)
+ m_corner_points.emplace_back(corner + coefficient.x() * incoming + coefficient.y() * outgoing);
+}
+
+// Rounds the corners of a scaled point sequence. A polygon closes implicitly, so all of its vertices
+// are corners; a polyline is an open path that keeps both of its ends, even where they coincide - a
+// path returning to where it started retraces its way back and is not a loop.
+static Points smooth_corners(const Points &points, const bool polygon, CornerSmoother &smoother)
+{
+ // A polygon has no free ends, so its first vertex is a corner like any other. Rounding it takes
+ // feeding the smoother the last vertex first, whose own output point is then dropped again.
+ size_t skip = polygon ? 1 : 0;
+
+ Points smoothed;
+ smoothed.reserve(2 * points.size());
+ auto emit = [&smoothed, &skip](const Vec2d &point) {
+ if (skip > 0) {
+ --skip;
+ return;
+ }
+ smoothed.emplace_back(coord_t(std::floor(point.x() + 0.5)), coord_t(std::floor(point.y() + 0.5)));
+ };
+
+ if (polygon)
+ smoother.push(points.back().cast(), emit);
+ for (const Point &point : points)
+ smoother.push(point.cast(), emit);
+ if (polygon)
+ // Wrap the first vertex around, so that the last one is a corner as well.
+ smoother.push(points.front().cast(), emit);
+ smoother.flush(emit);
+
+ if (polygon)
+ // The flushed point is the wrapped first vertex, which a polygon does not store.
+ smoothed.pop_back();
+ return smoothed;
+}
+
+void smooth_polyline_corners(Polyline &polyline, const double smooth_factor, const double tolerance,
+ const double max_corner_distance, const CornerFilter &corner_filter)
+{
+ CornerSmoother smoother(smooth_factor, tolerance, max_corner_distance, corner_filter);
+ if (!smoother.enabled() || polyline.size() < 3)
+ return;
+
+ polyline.points = smooth_corners(polyline.points, false, smoother);
+ // Rounding back to the integer grid may collapse neighbouring samples of a curve.
+ polyline.remove_duplicate_points();
+}
+
+void smooth_polylines_corners(Polylines &polylines, const double smooth_factor, const double tolerance,
+ const double max_corner_distance, const CornerFilter &corner_filter)
+{
+ if (sanitize_smooth_factor(smooth_factor) == 0.)
+ return;
+ for (Polyline &polyline : polylines)
+ smooth_polyline_corners(polyline, smooth_factor, tolerance, max_corner_distance, corner_filter);
+}
+
+void smooth_polygons_corners(Polygons &polygons, const double smooth_factor, const double tolerance,
+ const double max_corner_distance, const CornerFilter &corner_filter)
+{
+ CornerSmoother smoother(smooth_factor, tolerance, max_corner_distance, corner_filter);
+ if (!smoother.enabled())
+ return;
+
+ for (Polygon &polygon : polygons) {
+ if (polygon.size() < 3)
+ continue;
+ polygon.points = smooth_corners(polygon.points, true, smoother);
+ polygon.remove_duplicate_points();
+ // The curves of the first and of the last corner may have met on the segment they share. A
+ // polygon closes implicitly, so it must not repeat its first vertex at the end.
+ if (polygon.points.size() > 1 && polygon.points.front() == polygon.points.back())
+ polygon.points.pop_back();
+ }
+}
+
+} // namespace Slic3r
diff --git a/src/libslic3r/Fill/FillCornerSmoothing.hpp b/src/libslic3r/Fill/FillCornerSmoothing.hpp
new file mode 100644
index 0000000000..1852fc4c67
--- /dev/null
+++ b/src/libslic3r/Fill/FillCornerSmoothing.hpp
@@ -0,0 +1,108 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include "../libslic3r.h"
+#include "../Point.hpp"
+#include "../Polygon.hpp"
+#include "../Polyline.hpp"
+
+namespace Slic3r {
+
+// Orca: NaN or infinite factors disable the smoothing, everything else is clamped to <0, 1>.
+inline double sanitize_smooth_factor(double smooth_factor)
+{
+ return std::isfinite(smooth_factor) ? std::clamp(smooth_factor, 0., 1.) : 0.;
+}
+
+// Decides whether a corner may be replaced by the curve that leaves the path at `from` and rejoins it
+// at `to`, both in the coordinate system of the pushed points. Rounding cuts toward the inside of the
+// turn, so a path that is not clipped to the fill region afterwards needs this to stay inside it.
+using CornerFilter = std::function;
+
+// Orca: Replaces the sharp vertices of an infill path with curves that join the adjoining straight
+// legs with a continuous curvature, so the toolhead does not have to stop in every corner.
+// Points are pushed one by one, because the plane path fills produce their path on the fly, and
+// every point of the smoothed path is handed over to the caller supplied emit callback.
+// Fully smoothed adjacent corners meet at the midpoint of the segment they share, so the emitted
+// points may collapse onto each other once rounded to the integer grid of the caller. Dropping such
+// duplicates is left to the caller, which is the only one knowing that grid.
+class CornerSmoother
+{
+public:
+ // tolerance is the maximum chordal deviation of the flattened curves, in the units of the pushed
+ // points. max_corner_distance caps how far a curve may reach along a leg, in the same units; it
+ // bounds how far a rounded corner moves away from the original path, which matters where the legs
+ // are much longer than the spacing of the pattern. Zero leaves the reach uncapped.
+ CornerSmoother(double smooth_factor, double tolerance, double max_corner_distance = 0.,
+ CornerFilter corner_filter = {})
+ : m_corner_distance_ratio(0.5 * sanitize_smooth_factor(smooth_factor)), m_tolerance(tolerance),
+ m_max_corner_distance(max_corner_distance), m_corner_filter(std::move(corner_filter))
+ {}
+
+ bool enabled() const { return m_corner_distance_ratio > 0.; }
+
+ template void push(const Vec2d &point, Emit &emit)
+ {
+ if (m_pending == 0) {
+ emit(point);
+ m_previous = point;
+ } else if (m_pending > 1) {
+ round_corner(m_previous, m_corner, point);
+ for (const Vec2d &corner_point : m_corner_points)
+ emit(corner_point);
+ m_previous = m_corner;
+ }
+ m_corner = point;
+ m_pending = std::min(m_pending + 1, 2);
+ }
+
+ // Emits the last point of the path and prepares the smoother for a new one.
+ template void flush(Emit &emit)
+ {
+ if (m_pending > 1)
+ emit(m_corner);
+ m_pending = 0;
+ }
+
+private:
+ // Fills m_corner_points with the points replacing the corner vertex.
+ void round_corner(const Vec2d &previous, const Vec2d &corner, const Vec2d &next);
+ // Flattens the canonical corner curve of the given size and turn into coordinates of the
+ // (incoming, outgoing) basis of the corner. Cached, as an infill path repeats the same corner.
+ const std::vector& curve_coefficients(double corner_distance, const Vec2d &incoming, const Vec2d &outgoing);
+
+ // Fraction of the shorter adjoining segment consumed on each side of a corner. Half of a segment
+ // is the maximum, otherwise the curves of two adjacent corners would overlap.
+ const double m_corner_distance_ratio;
+ const double m_tolerance;
+ const double m_max_corner_distance;
+ const CornerFilter m_corner_filter;
+ std::vector m_corner_points;
+ // Cached flattening of the last corner, valid for corners of the same size and turn angle.
+ std::vector m_cached_coefficients;
+ double m_cached_distance { 0. };
+ double m_cached_cosine { 0. };
+ bool m_has_cached_coefficients { false };
+
+ Vec2d m_previous { Vec2d::Zero() };
+ Vec2d m_corner { Vec2d::Zero() };
+ // Number of points held back: none, the first point of a path, or a corner candidate.
+ int m_pending { 0 };
+};
+
+// Rounds the corners of already scaled paths in place. Paths of less than three points are left alone.
+// Both ends of a polyline are kept where they are, even when they coincide: such a path retraces its
+// way back and joining its ends would turn it into a loop. See CornerSmoother for max_corner_distance.
+void smooth_polyline_corners(Polyline &polyline, double smooth_factor, double tolerance,
+ double max_corner_distance = 0., const CornerFilter &corner_filter = {});
+void smooth_polylines_corners(Polylines &polylines, double smooth_factor, double tolerance,
+ double max_corner_distance = 0., const CornerFilter &corner_filter = {});
+// Polygons close implicitly, so every one of their vertices is a corner.
+void smooth_polygons_corners(Polygons &polygons, double smooth_factor, double tolerance,
+ double max_corner_distance = 0., const CornerFilter &corner_filter = {});
+
+} // namespace Slic3r
diff --git a/src/libslic3r/Fill/FillCrossHatch.cpp b/src/libslic3r/Fill/FillCrossHatch.cpp
index 571095eca4..98be5ef46b 100644
--- a/src/libslic3r/Fill/FillCrossHatch.cpp
+++ b/src/libslic3r/Fill/FillCrossHatch.cpp
@@ -3,6 +3,7 @@
#include "../Surface.hpp"
#include
#include "FillBase.hpp"
+#include "FillCornerSmoothing.hpp"
#include "FillCrossHatch.hpp"
namespace Slic3r {
@@ -205,6 +206,9 @@ void FillCrossHatch ::_fill_surface_single(
// shift the pattern to the actual space
for (Polyline &pl : polylines) { pl.translate(bb.min); }
+ // Orca: round the corners of the transition layers. The repeat layers are straight lines and stay as they are.
+ smooth_polylines_corners(polylines, params.smooth_factor, scaled(params.resolution));
+
// Apply multiline offset if needed
multiline_fill(polylines, params, spacing);
diff --git a/src/libslic3r/Fill/FillHoneycomb.cpp b/src/libslic3r/Fill/FillHoneycomb.cpp
index a595cdb664..82679541da 100644
--- a/src/libslic3r/Fill/FillHoneycomb.cpp
+++ b/src/libslic3r/Fill/FillHoneycomb.cpp
@@ -2,6 +2,7 @@
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
+#include "FillCornerSmoothing.hpp"
#include "FillHoneycomb.hpp"
namespace Slic3r {
@@ -70,6 +71,9 @@ void FillHoneycomb::_fill_surface_single(
}
p.rotate(-direction.first, m.hex_center);
p.simplify(5 * spacing); // simplify to 5x line width
+ // Orca: round the corners of the honeycomb cells. Done before the clipping, so that the
+ // curves are cut by the region boundary just like the sharp path would be.
+ smooth_polyline_corners(p, params.smooth_factor, scaled(params.resolution));
all_polylines.push_back(p);
}
}
diff --git a/src/libslic3r/Fill/FillLightning.cpp b/src/libslic3r/Fill/FillLightning.cpp
index 7937b9d129..77031b42e0 100644
--- a/src/libslic3r/Fill/FillLightning.cpp
+++ b/src/libslic3r/Fill/FillLightning.cpp
@@ -2,6 +2,7 @@
#include "../Print.hpp"
#include "../ShortestPath.hpp"
#include "FillBase.hpp"
+#include "FillCornerSmoothing.hpp"
#include "FillLightning.hpp"
#include "Lightning/Generator.hpp"
@@ -17,6 +18,19 @@ void Filler::_fill_surface_single(
const Layer &layer = generator->getTreesForLayer(this->layer_id);
Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled(0.5 * this->spacing - this->overlap));
+ // Orca: round the turns of the branches. Hairpins are left sharp, as they cannot be rounded, and
+ // the reach is capped: cutting a corner moves the branch, and a branch is as long as the object
+ // rather than as long as one cell of a pattern, so half of a leg would merge it with its neighbour
+ // instead of rounding the turn between them. Half the distance between two branches keeps them
+ // apart. With more than one line per infill wall the branches are printed as outlines drawn around
+ // them, and the outlines of branches that run into each other merge into a single one; moving a
+ // branch by more than a fraction of its printed width breaks such an outline up into separate
+ // loops, so that width bounds the reach as well.
+ const double branch_width = scaled(this->spacing) * params.multiline;
+ const double branch_spacing = branch_width / std::max(double(params.density), EPSILON);
+ const double max_reach = 0.5 * (params.multiline > 1 ? branch_width : branch_spacing);
+ smooth_polylines_corners(fill_lines, params.smooth_factor, scaled(params.resolution), max_reach);
+
// Apply multiline offset if needed
multiline_fill(fill_lines, params, spacing);
diff --git a/src/libslic3r/Fill/FillPlanePath.cpp b/src/libslic3r/Fill/FillPlanePath.cpp
index 7c4f285ac6..577aef0600 100644
--- a/src/libslic3r/Fill/FillPlanePath.cpp
+++ b/src/libslic3r/Fill/FillPlanePath.cpp
@@ -2,6 +2,7 @@
#include "../ShortestPath.hpp"
#include "../Surface.hpp"
+#include "FillCornerSmoothing.hpp"
#include "FillPlanePath.hpp"
namespace Slic3r {
@@ -288,145 +289,60 @@ static void generate_hilbert_curve(coord_t min_x, coord_t min_y, coord_t max_x,
}
}
-using QuinticBezier = std::array;
-
-static bool is_bezier_flat(const QuinticBezier &curve, const double deviation)
-{
- // A Bezier curve stays inside the convex hull of its control points. Therefore, keeping every
- // control point within a deviation-wide strip around the endpoint chord conservatively bounds the
- // flattening error. The cross product is the perpendicular distance scaled by the chord length;
- // comparing squared values avoids a square root.
- const Vec2d chord = curve.back() - curve.front();
- const double chord_length_sq = chord.squaredNorm();
- const double max_cross_sq = deviation * deviation * chord_length_sq;
-
- for (size_t i = 1; i + 1 < curve.size(); ++i) {
- const Vec2d offset = curve[i] - curve.front();
- const double cross = chord.x() * offset.y() - chord.y() * offset.x();
- if (cross * cross > max_cross_sq)
- return false;
- }
- return true;
-}
-
-static void subdivide_bezier(const QuinticBezier &curve, QuinticBezier &left, QuinticBezier &right)
-{
- // Split the curve at t = 0.5 using de Casteljau's algorithm. Each averaging level contributes one
- // control point to the left half and one to the right half; the latter is filled backwards to keep
- // both resulting control polygons in their original parameter direction.
- QuinticBezier subdivision = curve;
- left.front() = subdivision.front();
- right.back() = subdivision.back();
- for (size_t level = 1; level < curve.size(); ++level) {
- for (size_t i = 0; i + level < curve.size(); ++i)
- subdivision[i] = 0.5 * (subdivision[i] + subdivision[i + 1]);
- left[level] = subdivision.front();
- right[curve.size() - level - 1] = subdivision[curve.size() - level - 1];
- }
-}
-
-static void flatten_bezier(const QuinticBezier &curve, const double deviation, std::vector &output)
-{
- // Subdivide to at least depth 1 so a rounded corner cannot collapse to a single diagonal chord.
- // A uniform subdivision depth keeps samples at equal parameter intervals t = k / 2^depth,
- // avoiding abrupt segment-length jumps at adaptive-depth boundaries.
- static constexpr size_t max_depth = 16;
-
- std::vector subcurves(2);
- subdivide_bezier(curve, subcurves[0], subcurves[1]);
-
- for (size_t depth = 1; depth < max_depth; ++depth) {
- bool all_flat = true;
- for (const QuinticBezier &c : subcurves)
- if (!is_bezier_flat(c, deviation)) {
- all_flat = false;
- break;
- }
- if (all_flat)
- break;
- std::vector finer(subcurves.size() * 2);
- for (size_t i = 0; i < subcurves.size(); ++i)
- subdivide_bezier(subcurves[i], finer[i * 2], finer[i * 2 + 1]);
- subcurves = std::move(finer);
- }
-
- // The curve start is deliberately omitted so consecutive curve pieces can share it without duplication.
- output.reserve(output.size() + subcurves.size());
- for (const QuinticBezier &c : subcurves)
- output.emplace_back(c.back());
-}
-
+// Rounds the corners of the generated path on its way to the infill output.
template
-static void generate_smooth_hilbert_curve(
- coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
- const double corner_distance, Output &output)
+class SmoothingPolylineOutput
{
- // A Hilbert curve is defined on a square grid whose side is a power of two. As in the unsmoothed
- // generator, expand the larger requested dimension to the next valid Hilbert grid size. The output
- // clipper or the later region intersection removes the padded part of the traversal.
- size_t sz = 2;
- const size_t sz0 = std::max(max_x + 1 - min_x, max_y + 1 - min_y);
- while (sz < sz0)
- sz <<= 1;
+public:
+ SmoothingPolylineOutput(Output &output, const double smooth_factor, const double tolerance)
+ : m_output(output), m_smoother(smooth_factor, tolerance) {}
- const size_t point_count = sz * sz;
- output.reserve(point_count);
+ void reserve(size_t n) { m_output.reserve(n); }
+ void add_point(const Vec2d &pt) { auto emit = emitter(); m_smoother.push(pt, emit); }
+ // The smoother holds back the last point of the path until it knows there is no corner left to round.
+ void finish() { auto emit = emitter(); m_smoother.flush(emit); }
- // The caller normalizes resolution to the unit Hilbert grid; retain a finite positive tolerance
- // if this helper is invoked with an invalid resolution.
- const double deviation = resolution > 0. && std::isfinite(resolution) ? resolution : EPSILON;
- // Construct one canonical 90-degree corner from (-corner_distance, 0) to (0, corner_distance).
- // At each end, the first three control points are collinear and equally spaced: the tangent follows
- // the adjoining straight leg and the second derivative is zero. The endpoint curvature is therefore
- // zero, giving G2 joins to both legs. Every Hilbert turn is an oriented copy of this curve, so flatten
- // it only once to the requested chordal-deviation tolerance.
- const QuinticBezier corner_curve {{
- {-corner_distance, 0.}, {-0.7 * corner_distance, 0.}, {-0.4 * corner_distance, 0.},
- {0., 0.4 * corner_distance}, {0., 0.7 * corner_distance}, {0., corner_distance}
- }};
- std::vector curve_coefficients;
- flatten_bezier(corner_curve, deviation, curve_coefficients);
-
- auto translated_point = [min_x, min_y](size_t idx) {
- Point p = hilbert_n_to_xy(idx);
- return Point(p.x() + min_x, p.y() + min_y);
- };
- auto to_vec2d = [](const Point &p) { return Vec2d(double(p.x()), double(p.y())); };
- bool has_last_output = false;
- Vec2d last_output;
- // Fully smoothed adjacent corners may meet at the same segment midpoint. Suppress such duplicates
- // to avoid emitting zero-length extrusion segments.
- auto add_point = [&output, &has_last_output, &last_output](const Vec2d &point) {
- if (!has_last_output || point.x() != last_output.x() || point.y() != last_output.y()) {
- output.add_point(point);
- last_output = point;
- has_last_output = true;
- }
- };
-
- Vec2d previous = to_vec2d(translated_point(0));
- Vec2d corner = to_vec2d(translated_point(1));
- add_point(previous);
- // Replace each non-collinear Hilbert vertex by the canonical curve expressed in the local basis of
- // its incoming and outgoing unit vectors. Collinear vertices remain part of the straight polyline.
- for (size_t i = 1; i + 1 < point_count; ++i) {
- const Vec2d next = to_vec2d(translated_point(i + 1));
- const Vec2d incoming = (corner - previous).normalized();
- const Vec2d outgoing = (next - corner).normalized();
- const double cross = incoming.x() * outgoing.y() - incoming.y() * outgoing.x();
-
- if (std::abs(cross) < EPSILON) {
- add_point(corner);
- } else {
- add_point(corner - corner_distance * incoming);
- for (const Vec2d &coefficient : curve_coefficients)
- add_point(corner + coefficient.x() * incoming + coefficient.y() * outgoing);
- }
-
- previous = corner;
- corner = next;
+private:
+ // The curves of two adjacent corners meet at the midpoint of the segment they share, where they
+ // may round to the very same output point. Drop those, they would be zero length extrusions.
+ auto emitter()
+ {
+ return [this](const Vec2d &pt) {
+ const Point snapped = m_output.scaled(pt);
+ if (m_has_last_snapped && snapped == m_last_snapped)
+ return;
+ m_last_snapped = snapped;
+ m_has_last_snapped = true;
+ m_output.add_point(pt);
+ };
}
- add_point(corner);
+
+ Output &m_output;
+ CornerSmoother m_smoother;
+ Point m_last_snapped { Point::Zero() };
+ bool m_has_last_snapped { false };
+};
+
+// Runs the path generator against the concrete output type, optionally through the corner smoother.
+// The outputs do not share a virtual add_point(), so the type has to be resolved here.
+template
+static void generate_path(InfillPolylineOutput &output, const FillParams ¶ms, const double resolution, GenerateFn generate)
+{
+ const double smooth_factor = sanitize_smooth_factor(params.smooth_factor);
+ auto run = [smooth_factor, resolution, &generate](auto &out) {
+ if (smooth_factor == 0.) {
+ generate(out);
+ } else {
+ SmoothingPolylineOutput> smoothing(out, smooth_factor, resolution);
+ generate(smoothing);
+ smoothing.finish();
+ }
+ };
+
+ if (output.clips())
+ run(static_cast(output));
+ else
+ run(output);
}
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double /* resolution */, InfillPolylineOutput &output)
@@ -440,19 +356,8 @@ void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coo
void FillHilbertCurve::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
const FillParams ¶ms, InfillPolylineOutput &output)
{
- const double smooth_factor = std::isfinite(params.smooth_factor) ?
- std::clamp(params.smooth_factor, 0., 1.) : 0.;
- if (smooth_factor == 0.) {
- this->generate(min_x, min_y, max_x, max_y, resolution, output);
- return;
- }
-
- const double corner_distance = 0.5 * smooth_factor;
- if (output.clips())
- generate_smooth_hilbert_curve(
- min_x, min_y, max_x, max_y, resolution, corner_distance, static_cast(output));
- else
- generate_smooth_hilbert_curve(min_x, min_y, max_x, max_y, resolution, corner_distance, output);
+ generate_path(output, params, resolution,
+ [min_x, min_y, max_x, max_y](auto &out) { generate_hilbert_curve(min_x, min_y, max_x, max_y, out); });
}
template
@@ -495,4 +400,11 @@ void FillOctagramSpiral::generate(coord_t min_x, coord_t min_y, coord_t max_x, c
generate_octagram_spiral(min_x, min_y, max_x, max_y, output);
}
+void FillOctagramSpiral::generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
+ const FillParams ¶ms, InfillPolylineOutput &output)
+{
+ generate_path(output, params, resolution,
+ [min_x, min_y, max_x, max_y](auto &out) { generate_octagram_spiral(min_x, min_y, max_x, max_y, out); });
+}
+
} // namespace Slic3r
diff --git a/src/libslic3r/Fill/FillPlanePath.hpp b/src/libslic3r/Fill/FillPlanePath.hpp
index b4b25b73ae..a1e9068ca9 100644
--- a/src/libslic3r/Fill/FillPlanePath.hpp
+++ b/src/libslic3r/Fill/FillPlanePath.hpp
@@ -21,10 +21,10 @@ public:
void add_point(const Vec2d& pt) { m_out.emplace_back(this->scaled(pt)); }
Points&& result() { return std::move(m_out); }
virtual bool clips() const { return false; }
-
-protected:
+ // The output grid the generated points are snapped to.
const Point scaled(const Vec2d& fpt) const { return { coord_t(floor(fpt.x() * m_scale_out + 0.5)), coord_t(floor(fpt.y() * m_scale_out + 0.5)) }; }
+protected:
// Output polyline.
Points m_out;
@@ -93,6 +93,8 @@ public:
protected:
bool centered() const override { return true; }
void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution, InfillPolylineOutput &output) override;
+ void generate(coord_t min_x, coord_t min_y, coord_t max_x, coord_t max_y, const double resolution,
+ const FillParams ¶ms, InfillPolylineOutput &output) override;
};
} // namespace Slic3r
diff --git a/src/libslic3r/Fill/FillRectilinear.cpp b/src/libslic3r/Fill/FillRectilinear.cpp
index 8b40b8753c..0c82354b38 100644
--- a/src/libslic3r/Fill/FillRectilinear.cpp
+++ b/src/libslic3r/Fill/FillRectilinear.cpp
@@ -18,6 +18,7 @@
#include "../ShortestPath.hpp"
#include "../VariableWidth.hpp"
+#include "FillCornerSmoothing.hpp"
#include "FillRectilinear.hpp"
// #define SLIC3R_DEBUG
@@ -3364,6 +3365,10 @@ bool FillRectilinear::fill_surface_trapezoidal(
for (Polyline &pl : polylines)
pl.translate(rotate_vector.second);
+ // Orca: round the corners of the trapezoids. The straight base lines of the triangular family
+ // have no corner to round.
+ smooth_polylines_corners(polylines, params.smooth_factor, scaled(params.resolution));
+
// Apply multiline fill
multiline_fill(polylines, params, spacing);
diff --git a/src/libslic3r/GCode.cpp b/src/libslic3r/GCode.cpp
index 8e2e9f713c..18d805936e 100644
--- a/src/libslic3r/GCode.cpp
+++ b/src/libslic3r/GCode.cpp
@@ -6726,6 +6726,7 @@ void GCode::append_full_config(const Print &print, std::string &str)
"farthest_point_timelapse"sv,
"compatible_printers"sv,
"compatible_prints"sv,
+ "filament_colour_type"sv,
"print_host"sv,
"print_host_webui"sv,
"printhost_apikey"sv,
diff --git a/src/libslic3r/GCode/ExtrusionProcessor.hpp b/src/libslic3r/GCode/ExtrusionProcessor.hpp
index b282af8f4e..1d65e83f3e 100644
--- a/src/libslic3r/GCode/ExtrusionProcessor.hpp
+++ b/src/libslic3r/GCode/ExtrusionProcessor.hpp
@@ -19,6 +19,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -39,7 +40,11 @@ std::vector> estimate_points_properties(const POINTS&
const AABBTreeLines::LinesDistancer& unscaled_prev_layer,
float flow_width,
float max_line_length = -1.0f,
- float min_distance = -1.0f)
+ float min_distance = -1.0f,
+ // Maps an overhang distance onto the speed it will be printed at. Interior sampling
+ // needs it to tell which of the points it could add would change the G-code, and is
+ // skipped without it.
+ const std::function& distance_to_speed = {})
{
bool looped = input_points.front() == input_points.back();
std::function get_prev_index = [](size_t idx, size_t count) {
@@ -120,6 +125,107 @@ std::vector> estimate_points_properties(const POINTS&
points.push_back(next_point);
}
+ // ORCA: Interior sampling
+ // The passes below infer the support under a span from its endpoints alone, so an interior that is supported
+ // differently from both ends is invisible to them: the outer perimeter of an overhang whose ends are caged by
+ // full height walls reads as supported along its whole length. Probe the interior, keep the samples the
+ // endpoint interpolation fails to predict, and bisect either side of each one, so a span that is only partly
+ // unsupported gets points where its support actually changes instead of one reading spread across all of it.
+ if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS && min_distance > 0 && distance_to_speed) {
+ // Probe at least this densely before treating matching samples as evidence that a span is uniform. The
+ // segmentation pass below only splits lines of 2mm or more, and every pass here drops points closer
+ // together than min_spacing, so finer discovery would not produce a more precise speed transition.
+ const double max_probe_spacing = std::max(2., 4. * min_spacing);
+ // A backstop for that length test, which on a non-finite length would never be met.
+ constexpr int max_bisection_depth = 10;
+ // Whether two readings are interchangeable. A segment is printed at the lower of the speeds its ends
+ // read, so a sample that agrees on speed with what is already known cannot change the G-code, whatever
+ // its distance says. The distances themselves are far too coarse a stand-in for this: the speed sections
+ // interpolate, so readings a small fraction of min_distance apart can still be tens of mm/s apart.
+ // The tolerance matches the one GCode.cpp applies when it decides a path has a variable speed at all.
+ auto same_speed = [&distance_to_speed](float a, float b) {
+ return std::abs(distance_to_speed(a) - distance_to_speed(b)) <= 1.f;
+ };
+ // Whether the first reading is printed slower than the second, once they are known to differ.
+ auto prints_slower = [&distance_to_speed](float a, float b) { return distance_to_speed(a) < distance_to_speed(b); };
+
+ // Part of a segment still to bisect: its positions along the segment and bisections left.
+ struct Subspan { double t0, t1; int depth; };
+
+ std::vector> sampled_points; // Populated lazily, on the first insertion
+ std::vector> interior; // Samples of one segment, keyed by position along it
+ std::vector pending;
+
+ for (size_t point_idx = 0; point_idx + 1 < points.size(); ++point_idx) {
+ const ExtendedPoint& curr = points[point_idx];
+ const ExtendedPoint& next = points[point_idx + 1];
+ const Vec step = next.position - curr.position;
+ const double line_len = step.norm();
+
+ interior.clear();
+ if (line_len >= max_probe_spacing)
+ pending.push_back({0., 1., max_bisection_depth});
+
+ while (!pending.empty()) {
+ const Subspan subspan = pending.back();
+ pending.pop_back();
+ if (subspan.depth <= 0 || (subspan.t1 - subspan.t0) * line_len < max_probe_spacing)
+ continue;
+
+ const double t = 0.5 * (subspan.t0 + subspan.t1);
+ auto [distance, nearest_line, x] = unscaled_prev_layer.template distance_from_lines_extra(
+ (curr.position + t * step).template cast());
+ const float sampled = float(distance + boundary_offset);
+
+ interior.emplace_back(t, sampled);
+ pending.push_back({subspan.t0, t, subspan.depth - 1});
+ pending.push_back({t, subspan.t1, subspan.depth - 1});
+ }
+
+ if (!interior.empty()) {
+ std::sort(interior.begin(), interior.end(),
+ [](const std::pair& l, const std::pair& r) { return l.first < r.first; });
+ // Coarse probing keeps every sample it took until this pass can see which ones bracket a speed
+ // transition. Matching samples cannot be discarded during discovery: one may be the last
+ // supported point before a narrow unsupported pocket found by a later probe.
+ size_t kept = 0;
+ for (size_t i = 0; i < interior.size(); ++i) {
+ const float sample = interior[i].second;
+ const bool at_start = kept == 0; // Nothing kept yet, so the segment's own start precedes it
+ const bool at_end = i + 1 == interior.size(); // And nothing follows the last sample but the segment's end
+ const float before = at_start ? curr.distance : interior[kept - 1].second;
+ const float after = at_end ? next.distance : interior[i + 1].second;
+ // A sample is worth a point in the path only where it prints at a different speed from the
+ // readings either side of it. Differing from one of the segment's own ends is not enough on
+ // its own where the sample is the faster of the two: the segmentation pass below already
+ // ends the slowdown an end reads, at a distance taken from how far out that end is rather
+ // than from wherever bisection happened to stop, and a point here would leave the span
+ // beside the end too short for that pass to run at all. Support an end cannot account for,
+ // where the interior is the slower reading, is exactly what this pass is here to find.
+ const bool worth_before = !same_speed(sample, before) && (!at_start || prints_slower(sample, before));
+ const bool worth_after = !same_speed(sample, after) && (!at_end || prints_slower(sample, after));
+ if (worth_before || worth_after)
+ interior[kept++] = interior[i];
+ }
+ interior.resize(kept);
+ }
+
+ if (!interior.empty() && sampled_points.empty()) {
+ sampled_points.reserve(points.size() + 8);
+ sampled_points.assign(points.begin(), points.begin() + point_idx + 1);
+ }
+ if (!sampled_points.empty()) {
+ // Only a sub-span of max_probe_spacing or more is ever bisected, so these sit at least
+ // 2 * min_spacing apart, and need none of the filtering the passes either side of this one do.
+ for (const auto& [t, distance] : interior)
+ sampled_points.push_back({curr.position + t * step, distance});
+ sampled_points.push_back(next);
+ }
+ }
+ if (!sampled_points.empty())
+ points = std::move(sampled_points);
+ }
+
// Segmentation handling
if (PREV_LAYER_BOUNDARY_OFFSET && ADD_INTERSECTIONS) {
std::vector> new_points;
@@ -362,9 +468,28 @@ public:
smallest_distance_with_lower_speed=-1.f;
// Orca: Pass to the point properties estimator the smallest ovehang distance that triggers a slowdown (smallest_distance_with_lower_speed)
+ auto calculate_speed = [&speed_sections, &original_speed](float distance) {
+ float final_speed;
+ if (distance <= speed_sections.front().first) {
+ final_speed = original_speed;
+ } else if (distance >= speed_sections.back().first) {
+ final_speed = speed_sections.back().second;
+ } else {
+ size_t section_idx = 0;
+ while (distance > speed_sections[section_idx + 1].first) {
+ section_idx++;
+ }
+ float t = (distance - speed_sections[section_idx].first) /
+ (speed_sections[section_idx + 1].first - speed_sections[section_idx].first);
+ t = std::clamp(t, 0.0f, 1.0f);
+ final_speed = (1.0f - t) * speed_sections[section_idx].second + t * speed_sections[section_idx + 1].second;
+ }
+ return round(final_speed);
+ };
+
std::vector> extended_points =
estimate_points_properties(path.polyline.points, prev_layer_boundaries[current_object], path.width, -1,
- smallest_distance_with_lower_speed);
+ smallest_distance_with_lower_speed, calculate_speed);
const auto width_inv = 1.0f / path.width;
std::vector processed_points;
processed_points.reserve(extended_points.size());
@@ -423,25 +548,6 @@ public:
}
}
- auto calculate_speed = [&speed_sections, &original_speed](float distance) {
- float final_speed;
- if (distance <= speed_sections.front().first) {
- final_speed = original_speed;
- } else if (distance >= speed_sections.back().first) {
- final_speed = speed_sections.back().second;
- } else {
- size_t section_idx = 0;
- while (distance > speed_sections[section_idx + 1].first) {
- section_idx++;
- }
- float t = (distance - speed_sections[section_idx].first) /
- (speed_sections[section_idx + 1].first - speed_sections[section_idx].first);
- t = std::clamp(t, 0.0f, 1.0f);
- final_speed = (1.0f - t) * speed_sections[section_idx].second + t * speed_sections[section_idx + 1].second;
- }
- return round(final_speed);
- };
-
float extrusion_speed = std::min(calculate_speed(curr.distance), calculate_speed(next.distance));
// ORCA: Clamp resulting speed to lowest of calculated speed based on the overhang values and the current speed
// Fixes bug where resulting overhang speed is higher than the current speed due to (for example) volumetric flow limits.
diff --git a/src/libslic3r/Model.cpp b/src/libslic3r/Model.cpp
index c35987adef..ab6a685a22 100644
--- a/src/libslic3r/Model.cpp
+++ b/src/libslic3r/Model.cpp
@@ -3246,9 +3246,9 @@ double Model::findMaxSpeed(const ModelObject* object) {
if (objectKey == "outer_wall_speed")
externalPerimeterSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
if (objectKey == "small_perimeter_speed")
- smallPerimeterSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
+ smallPerimeterSpeedObj = object->config.get().opt_float_or_percent_nullable(objectKey, 0).get_abs_value(externalPerimeterSpeedObj);
if (objectKey == "small_support_perimeter_speed")
- smallSupportPerimeterSpeedObj = object->config.get().opt_float_nullable(objectKey, 0);
+ smallSupportPerimeterSpeedObj = object->config.get().opt_float_or_percent_nullable(objectKey, 0).get_abs_value(supportSpeedObj);
}
objMaxSpeed = std::max(perimeterSpeedObj, std::max(externalPerimeterSpeedObj, std::max(infillSpeedObj, std::max(solidInfillSpeedObj, std::max(topSolidInfillSpeedObj, std::max(supportSpeedObj, std::max(smallPerimeterSpeedObj, std::max(smallSupportPerimeterSpeedObj, objMaxSpeed))))))));
if (objMaxSpeed <= 0) objMaxSpeed = 250.;
diff --git a/src/libslic3r/PresetBundle.cpp b/src/libslic3r/PresetBundle.cpp
index 5557d36891..e66ae064f0 100644
--- a/src/libslic3r/PresetBundle.cpp
+++ b/src/libslic3r/PresetBundle.cpp
@@ -49,7 +49,6 @@ static std::vector s_project_options {
"filament_multi_colour",
"wipe_tower_x",
"wipe_tower_y",
- "wipe_tower_rotation_angle",
"curr_bed_type",
"flush_multiplier",
// Fast-purge mode: project-level purge control, inert at Default.
diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp
index fdb20253d6..8083da954e 100644
--- a/src/libslic3r/PrintConfig.cpp
+++ b/src/libslic3r/PrintConfig.cpp
@@ -3469,9 +3469,8 @@ void PrintConfigDef::init_fff_params()
def = this->add("sparse_infill_smooth_factor", coPercent);
def->label = L("Sparse infill smooth factor");
def->category = L("Strength");
- def->tooltip = L("Controls how strongly sparse infill corners are rounded. 0% keeps the original right-angle path, "
- "while 100% produces the largest possible curves between adjacent infill lines. "
- "Currently applies only to the Hilbert Curve.");
+ def->tooltip = L("Controls how strongly sparse infill corners are rounded. 0% keeps the original sharp path, "
+ "while 100% produces the largest possible curves between adjacent infill lines.");
def->sidetext = "%";
def->min = 0;
def->max = 100;
@@ -10426,6 +10425,16 @@ int DynamicPrintConfig::update_values_from_multi_to_multi_2(const std::vector &variant_index, int stride)
+{
+ // A single-value object or region override applies to every nozzle variant.
+ std::vector indices = variant_index;
+ if (source.size() == 1 && !source.is_nil(0))
+ std::fill(indices.begin(), indices.end(), 0);
+ target.set_to_index(&source, indices, stride);
+}
+
//used for object/region config
//use the smallest of multiple to single
@@ -11503,7 +11512,7 @@ void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPr
else {
ConfigOptionVectorBase* opt_vec_src = static_cast(opt_src);
const ConfigOptionVectorBase* opt_vec_dest = static_cast(opt_dest);
- opt_vec_src->set_to_index(opt_vec_dest, variant_index, stride);
+ set_variant_override(*opt_vec_src, *opt_vec_dest, variant_index, stride);
}
}
}
diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp
index 6029d5bd88..26a708b78d 100644
--- a/src/libslic3r/PrintConfig.hpp
+++ b/src/libslic3r/PrintConfig.hpp
@@ -146,6 +146,29 @@ inline bool is_separable_infill_pattern(InfillPattern pattern)
}
}
+// Orca: Infill patterns that round their corners by the "sparse_infill_smooth_factor" option.
+// Grid, Triangles and Tri-hexagon only do so in their trapezoidal form, which is generated with more
+// than one line per infill wall; a single line makes them plain crossing lines with nothing to round.
+inline bool is_smoothable_infill_pattern(InfillPattern pattern, int multiline = 1)
+{
+ switch (pattern) {
+ case ipHilbertCurve:
+ case ipOctagramSpiral:
+ case ipLightning:
+ case ipHoneycomb:
+ case ip3DHoneycomb:
+ case ipConcentric:
+ case ipCrossHatch:
+ return true;
+ case ipGrid:
+ case ipTriangles:
+ case ipStars:
+ return multiline > 1;
+ default:
+ return false;
+ }
+}
+
enum class IroningType {
NoIroning,
TopSurfaces,
@@ -842,6 +865,9 @@ extern std::set printer_options_with_variant_1;
extern std::set printer_options_with_variant_2;
extern std::set empty_options;
+void set_variant_override(ConfigOptionVectorBase &target, const ConfigOptionVectorBase &source,
+ const std::vector &variant_index, int stride = 1);
+
extern std::set filament_dev_options;
extern void update_static_print_config_from_dynamic(ConfigBase& config, const DynamicPrintConfig& dest_config, std::vector variant_index, std::set& key_set1, int stride = 1);
@@ -2394,6 +2420,55 @@ static void set_flush_volumes_matrix(std::vector &out_matrix, const std::vect
}
}
+template
+static bool has_zero_flush_volume_for_used_filaments(const std::vector &fv_matrix,
+ const std::vector &flush_multipliers,
+ const std::vector &used_filaments)
+{
+ if (used_filaments.size() < 2 || flush_multipliers.empty())
+ return false;
+
+ if (fv_matrix.size() % flush_multipliers.size() != 0)
+ return false;
+
+ const size_t matrix_len = fv_matrix.size() / flush_multipliers.size();
+ const size_t row_len = size_t(std::sqrt(double(matrix_len)));
+ if (row_len < 2 || row_len * row_len != matrix_len)
+ return false;
+
+ std::vector filtered_filaments;
+ filtered_filaments.reserve(used_filaments.size());
+ for (int filament_id : used_filaments) {
+ if (filament_id <= 0 || filament_id > int(row_len))
+ continue;
+ if (std::find(filtered_filaments.begin(), filtered_filaments.end(), filament_id) == filtered_filaments.end())
+ filtered_filaments.push_back(filament_id);
+ }
+ if (filtered_filaments.size() < 2)
+ return false;
+
+ for (T multiplier : flush_multipliers) {
+ if (multiplier == 0)
+ return true;
+ }
+
+ for (size_t nozzle_idx = 0; nozzle_idx < flush_multipliers.size(); nozzle_idx++) {
+ const size_t block_offset = nozzle_idx * matrix_len;
+ for (int from_id : filtered_filaments) {
+ for (int to_id : filtered_filaments) {
+ if (from_id == to_id)
+ continue;
+
+ const size_t matrix_idx = block_offset + size_t(from_id - 1) * row_len + size_t(to_id - 1);
+ if (matrix_idx < fv_matrix.size() && fv_matrix[matrix_idx] == 0)
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
size_t get_extruder_index(const GCodeConfig& config, unsigned int filament_id);
} // namespace Slic3r
diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp
index b2a92f11a6..8368de1a4f 100644
--- a/src/libslic3r/PrintObject.cpp
+++ b/src/libslic3r/PrintObject.cpp
@@ -3812,7 +3812,7 @@ static void apply_to_print_region_config(PrintRegionConfig &out, const DynamicPr
else {
ConfigOptionVectorBase* opt_vec_src = static_cast(my_opt);
const ConfigOptionVectorBase* opt_vec_dest = static_cast(it->second.get());
- opt_vec_src->set_to_index(opt_vec_dest, variant_index, 1);
+ set_variant_override(*opt_vec_src, *opt_vec_dest, variant_index);
}
}
}
diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt
index 54e2592b5f..57bcaa6e82 100644
--- a/src/slic3r/CMakeLists.txt
+++ b/src/slic3r/CMakeLists.txt
@@ -620,6 +620,8 @@ set(SLIC3R_GUI_SOURCES
plugin/host/PluginHostSlicing.cpp
plugin/host/PluginHostUi.cpp
plugin/host/PluginHostUi.hpp
+ plugin/host/PluginPages.cpp
+ plugin/host/PluginPages.hpp
plugin/CloudPluginService.cpp
plugin/CloudPluginService.hpp
plugin/PluginFsUtils.cpp
@@ -640,6 +642,9 @@ set(SLIC3R_GUI_SOURCES
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapabilityTrampoline.hpp
+ plugin/pluginTypes/pages/PagesPluginCapability.hpp
+ plugin/pluginTypes/pages/PagesPluginCapability.cpp
+ plugin/pluginTypes/pages/PagesPluginCapabilityTrampoline.hpp
plugin/pluginTypes/script/ScriptPluginCapability.hpp
plugin/pluginTypes/script/ScriptPluginCapability.cpp
plugin/pluginTypes/script/ScriptPluginCapabilityTrampoline.hpp
diff --git a/src/slic3r/GUI/Auxiliary.cpp b/src/slic3r/GUI/Auxiliary.cpp
index 95244436a3..13ca173eb6 100644
--- a/src/slic3r/GUI/Auxiliary.cpp
+++ b/src/slic3r/GUI/Auxiliary.cpp
@@ -869,11 +869,11 @@ void AuxiliaryPanel::init_tabpanel()
m_assembly_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::ASSEMBLY_GUIDE);
m_others_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::OTHERS);
- m_tabpanel->AddPage(m_designer_panel, _L("Basic Info"), "", true);
- m_tabpanel->AddPage(m_pictures_panel, _L("Pictures"), "", false);
- m_tabpanel->AddPage(m_bill_of_materials_panel, _L("Bill of Materials"), "", false);
- m_tabpanel->AddPage(m_assembly_panel, _L("Assembly Guide"), "", false);
- m_tabpanel->AddPage(m_others_panel, _L("Others"), "", false);
+ m_tabpanel->AddPage(m_designer_panel, _L("Basic Info"), true);
+ m_tabpanel->AddPage(m_pictures_panel, _L("Pictures"), false);
+ m_tabpanel->AddPage(m_bill_of_materials_panel, _L("Bill of Materials"), false);
+ m_tabpanel->AddPage(m_assembly_panel, _L("Assembly Guide"), false);
+ m_tabpanel->AddPage(m_others_panel, _L("Others"), false);
}
wxWindow *AuxiliaryPanel::create_side_tools()
diff --git a/src/slic3r/GUI/CAD/DesignPanel.cpp b/src/slic3r/GUI/CAD/DesignPanel.cpp
index e806e46d39..1d7bb429f8 100644
--- a/src/slic3r/GUI/CAD/DesignPanel.cpp
+++ b/src/slic3r/GUI/CAD/DesignPanel.cpp
@@ -8968,7 +8968,7 @@ void DesignPanel::on_commit()
sync_recipe_to_model();
if (wxGetApp().mainframe != nullptr)
- wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
}
CadFeature DesignPanel::build_candidate(Tool t) const
diff --git a/src/slic3r/GUI/CalibrationPanel.cpp b/src/slic3r/GUI/CalibrationPanel.cpp
index b006509adf..bdc79c1c8e 100644
--- a/src/slic3r/GUI/CalibrationPanel.cpp
+++ b/src/slic3r/GUI/CalibrationPanel.cpp
@@ -488,7 +488,6 @@ void CalibrationPanel::init_tabpanel() {
selected = true;
m_tabpanel->AddPage(m_cali_panels[i],
get_calibration_type_name(m_cali_panels[i]->get_calibration_mode()),
- "",
selected);
}
diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp
index 3885a391b8..de94bb6b4b 100644
--- a/src/slic3r/GUI/ConfigManipulation.cpp
+++ b/src/slic3r/GUI/ConfigManipulation.cpp
@@ -752,7 +752,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
bool has_top_shell = has_top_shell_layers && config->option("top_surface_density")->value > 0;
bool has_bottom_shell = config->opt_int("bottom_shell_layers") > 0;
bool has_solid_infill = has_top_shell_layers || has_bottom_shell;
- toggle_line("sparse_infill_smooth_factor", pattern == ipHilbertCurve);
+ toggle_line("sparse_infill_smooth_factor", is_smoothable_infill_pattern(pattern, config->opt_int("fill_multiline")));
toggle_field("top_surface_pattern", has_top_shell);
toggle_field("bottom_surface_pattern", has_bottom_shell);
toggle_field("top_surface_density", has_top_shell_layers);
diff --git a/src/slic3r/GUI/Downloader.cpp b/src/slic3r/GUI/Downloader.cpp
index c61b2716fc..0d37a0eca6 100644
--- a/src/slic3r/GUI/Downloader.cpp
+++ b/src/slic3r/GUI/Downloader.cpp
@@ -134,7 +134,7 @@ void Downloader::start_download(const std::string& full_url)
Plater* plater = wxGetApp().plater();
mainframe->Freeze();
- mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
+ mainframe->select_tab(TAB_ID_PREPARE);
plater->select_view_3D("3D");
plater->select_view("plate");
plater->get_current_canvas3D()->zoom_to_bed();
diff --git a/src/slic3r/GUI/Field.cpp b/src/slic3r/GUI/Field.cpp
index 8d05de13a4..dbbf5ec5e2 100644
--- a/src/slic3r/GUI/Field.cpp
+++ b/src/slic3r/GUI/Field.cpp
@@ -331,8 +331,10 @@ void Field::PostInitialize()
}
default: break;
}
- if (tab_id >= 0)
- wxGetApp().mainframe->select_tab(tab_id);
+ if (tab_id >= 0) {
+ static constexpr const char* kShortcutTabIds[] = {TAB_ID_HOME, TAB_ID_PREPARE, TAB_ID_PREVIEW, TAB_ID_MONITOR};
+ wxGetApp().mainframe->select_tab(kShortcutTabIds[tab_id]);
+ }
if (tab_id > 0)
// tab panel should be focused for correct navigation between tabs
wxGetApp().tab_panel()->SetFocus();
diff --git a/src/slic3r/GUI/GLCanvas3D.cpp b/src/slic3r/GUI/GLCanvas3D.cpp
index 9924859d9f..8532199cc1 100644
--- a/src/slic3r/GUI/GLCanvas3D.cpp
+++ b/src/slic3r/GUI/GLCanvas3D.cpp
@@ -2909,7 +2909,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
float x = dynamic_cast(proj_cfg.option("wipe_tower_x"))->get_at(plate_id);
float y = dynamic_cast(proj_cfg.option("wipe_tower_y"))->get_at(plate_id);
float w = dynamic_cast(m_config->option("prime_tower_width"))->value;
- float a = dynamic_cast(proj_cfg.option("wipe_tower_rotation_angle"))->value;
+ float a = dynamic_cast(m_config->option("wipe_tower_rotation_angle"))->value;
// BBS
float v = dynamic_cast(m_config->option("prime_volume"))->value;
Vec3d plate_origin = ppl.get_plate(plate_id)->get_origin();
@@ -9328,7 +9328,7 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar()
view3d_canvas->get_gizmos_manager().reset_all_states(); // close all gizmos
view3d_canvas->reload_scene(true);
}
- app.mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
+ app.mainframe->select_tab(TAB_ID_PREPARE);
}
}
});
@@ -10737,9 +10737,8 @@ void GLCanvas3D::_set_warning_notification(EWarning warning, bool state)
wxString region = L"en";
if (language.find("zh") == 0)
region = L"zh";
- // Use the generic dual-nozzle PLA+PETG guide rather than the H2D-specific page
- // so the link is relevant for all dual-extrusion printers, not just Bambu H2D. (#12073)
- wxGetApp().open_browser_with_warning_dialog(wxString::Format(L"https://wiki.bambulab.com/%s/filament-acc/filament/pla-and-petg-dual-extrusion", region));
+ // Although this link looks like it's only for the H2D, its guidance is generic.
+ wxGetApp().open_browser_with_warning_dialog(wxString::Format(L"https://wiki.bambulab.com/%s/filament-acc/filament/h2d-pla-and-petg-mutual-support", region));
return false;
});
}
@@ -10873,24 +10872,14 @@ bool GLCanvas3D::is_flushing_matrix_error() {
if (!Sidebar::should_show_SEMM_buttons())
return false;
+ std::vector plate_extruders = wxGetApp().plater()->get_partplate_list().get_curr_plate()->get_extruders(true);
+ if (plate_extruders.size() < 2)
+ return false;
+
const auto &project_config = wxGetApp().preset_bundle->project_config;
const std::vector &config_matrix = (project_config.option("flush_volumes_matrix"))->values;
const std::vector &config_multiplier = (project_config.option("flush_multiplier"))->values;
-
- for (auto multiplier : config_multiplier) {
- if (multiplier == 0) return true;
- }
-
- int matrix_len = config_matrix.size() / config_multiplier.size();
- int row_len = std::sqrt(matrix_len);
- for (int i = 0; i < config_matrix.size(); i++)
- {
- int relative_id = i % matrix_len;
- int row_id = relative_id / row_len;
- int col_id = relative_id % row_len;
- if (row_id != col_id && config_matrix[i] == 0) return true;
- }
- return false;
+ return has_zero_flush_volume_for_used_filaments(config_matrix, config_multiplier, plate_extruders);
}
bool GLCanvas3D::_is_any_volume_outside() const
diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp
index 81f2cb8c5f..e982b5d026 100644
--- a/src/slic3r/GUI/GUI_App.cpp
+++ b/src/slic3r/GUI/GUI_App.cpp
@@ -813,12 +813,12 @@ void GUI_App::post_init()
m_open_method = "url";
} else {
if (this->init_params->input_gcode) {
- mainframe->select_tab(size_t(MainFrame::tp3DEditor));
+ mainframe->select_tab(TAB_ID_PREPARE);
plater_->select_view_3D("3D");
this->plater()->load_gcode(from_u8(this->init_params->input_files.front()));
m_open_method = "gcode";
} else {
- mainframe->select_tab(size_t(MainFrame::tp3DEditor));
+ mainframe->select_tab(TAB_ID_PREPARE);
plater_->select_view_3D("3D");
wxArrayString input_files;
for (auto& file : this->init_params->input_files) {
@@ -852,7 +852,7 @@ void GUI_App::post_init()
mainframe->Freeze();
#endif
plater_->canvas3D()->enable_render(false);
- mainframe->select_tab(size_t(MainFrame::tp3DEditor));
+ mainframe->select_tab(TAB_ID_PREPARE);
plater_->select_view_3D("3D");
//BBS init the opengl resource here
if (!plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen() ||
@@ -890,9 +890,9 @@ void GUI_App::post_init()
}
}
if (is_editor())
- mainframe->select_tab(size_t(0));
+ mainframe->select_tab(TAB_ID_HOME);
if (app_config->get("default_page") == "1")
- mainframe->select_tab(size_t(1));
+ mainframe->select_tab(TAB_ID_PREPARE);
#ifndef __linux__
mainframe->Thaw();
#endif
@@ -1829,10 +1829,10 @@ bool GUI_App::hot_reload_network_plugin()
wxWindowDisabler disabler;
if (mainframe) {
- int current_tab = mainframe->m_tabpanel->GetSelection();
- if (current_tab == MainFrame::TabPosition::tpMonitor) {
+ wxString current_tab = mainframe->m_tabpanel->GetSelectedPageName();
+ if (current_tab == TAB_ID_MONITOR) {
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": navigating away from Monitor tab before unload";
- mainframe->m_tabpanel->SetSelection(MainFrame::TabPosition::tp3DEditor);
+ mainframe->m_tabpanel->SelectPageByName(TAB_ID_PREPARE);
}
}
@@ -2851,6 +2851,16 @@ void GUI_App::init_plugin_gui_wiring()
plugin_mgr.subscribe_on_unload_callback([refresh_plugins_dialog](const std::string&) { refresh_plugins_dialog(); });
plugin_mgr.subscribe_on_load_callback(NetworkAgentFactory::register_python_plugin);
plugin_mgr.subscribe_on_unload_callback(NetworkAgentFactory::deregister_python_plugin);
+ plugin_mgr.subscribe_on_load_callback([](const std::string& plugin_key) {
+ if (wxTheApp == nullptr || wxGetApp().is_closing() || wxGetApp().mainframe == nullptr)
+ return;
+ wxGetApp().mainframe->plugin_pages().on_plugin_register(plugin_key);
+ });
+ plugin_mgr.subscribe_on_unload_callback([](const std::string& plugin_key) {
+ if (wxTheApp == nullptr || wxGetApp().is_closing() || wxGetApp().mainframe == nullptr)
+ return;
+ wxGetApp().mainframe->plugin_pages().on_plugin_deregister(plugin_key);
+ });
plugin_mgr.subscribe_on_load_callback(refresh_printer_agent_dropdown_after_load);
plugin_mgr.subscribe_on_unload_callback(switch_printer_agent_after_unload);
plugin_mgr.subscribe_on_capability_load_callback(
@@ -2866,11 +2876,15 @@ void GUI_App::init_plugin_gui_wiring()
if (Plater* plater = wxGetApp().plater())
plater->revalidate_current_plate_if_plugins_missing();
});
+ if (capability.type == PluginCapabilityType::Pages && wxTheApp && !wxGetApp().is_closing() && wxGetApp().mainframe)
+ wxGetApp().mainframe->plugin_pages().on_cap_register(capability);
});
plugin_mgr.subscribe_on_capability_unload_callback(
[refresh_plugins_dialog, switch_printer_agent_after_unload](const PluginCapabilityId& capability) {
if (capability.type == PluginCapabilityType::PrinterConnection)
NetworkAgentFactory::deregister_python_printer_agent(capability.plugin_key, capability.name);
+ if (capability.type == PluginCapabilityType::Pages && wxTheApp && !wxGetApp().is_closing() && wxGetApp().mainframe)
+ wxGetApp().mainframe->plugin_pages().on_cap_deregister(capability);
refresh_plugins_dialog();
switch_printer_agent_after_unload(capability.plugin_key);
});
@@ -3273,15 +3287,12 @@ bool GUI_App::on_init_inner()
}
} */
- copy_network_if_available();
if (scrn) {
scrn->SetText(_L("Loading Plugins") + dots, 20);
wxYield();
}
- on_init_network();
-
// Initialize plugins after network then register on_load callbacks so once the plugin loads finish, it gets registered automatically.
// initialize() also installs the libslic3r hooks (capability resolver,
// slicing-pipeline dispatcher) via plugin_hooks::install() -- no
@@ -3310,6 +3321,9 @@ bool GUI_App::on_init_inner()
}
}
+ copy_network_if_available();
+ on_init_network();
+
if (m_agent)
plugin_mgr.set_cloud_agent(std::dynamic_pointer_cast(m_agent->get_cloud_agent()));
@@ -3382,7 +3396,7 @@ bool GUI_App::on_init_inner()
mainframe = new MainFrame();
// hide settings tabs after first Layout
if (is_editor()) {
- mainframe->select_tab(size_t(0));
+ mainframe->select_tab(TAB_ID_HOME);
}
sidebar().obj_list()->init();
@@ -4594,7 +4608,7 @@ void GUI_App::recreate_GUI(const wxString &msg_name)
mainframe = new MainFrame();
if (is_editor())
// hide settings tabs after first Layout
- mainframe->select_tab(size_t(MainFrame::tp3DEditor));
+ mainframe->select_tab(TAB_ID_PREPARE);
// Propagate model objects to object list.
sidebar().obj_list()->init();
//sidebar().aux_list()->init_auxiliary();
@@ -9865,7 +9879,7 @@ bool GUI_App::check_url_association(std::wstring url_prefix, std::wstring& reg_b
{
reg_bin = L"";
#ifdef WIN32
- wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
+ wxRegKey key_full(wxRegKey::HKCU, L"Software\\Classes\\" + url_prefix + L"\\shell\\open\\command");
if (!key_full.Exists()) {
return false;
}
@@ -9891,8 +9905,8 @@ void GUI_App::associate_url(std::wstring url_prefix)
wxString key_string = "\"" + wbinary + "\" \"%1\"";
- wxRegKey key_first(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix);
- wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
+ wxRegKey key_first(wxRegKey::HKCU, L"Software\\Classes\\" + url_prefix);
+ wxRegKey key_full(wxRegKey::HKCU, L"Software\\Classes\\" + url_prefix + L"\\shell\\open\\command");
if (!key_first.Exists()) {
key_first.Create(false);
}
@@ -9912,7 +9926,7 @@ void GUI_App::disassociate_url(std::wstring url_prefix)
#ifdef WIN32
if (is_running_in_msix())
return;
- wxRegKey key_full(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix + "\\shell\\open\\command");
+ wxRegKey key_full(wxRegKey::HKCU, L"Software\\Classes\\" + url_prefix + L"\\shell\\open\\command");
if (!key_full.Exists()) {
return;
}
diff --git a/src/slic3r/GUI/HMS.cpp b/src/slic3r/GUI/HMS.cpp
index 4d67a398f7..5471c16064 100644
--- a/src/slic3r/GUI/HMS.cpp
+++ b/src/slic3r/GUI/HMS.cpp
@@ -1,6 +1,7 @@
#include "HMS.hpp"
#include "GUI.hpp"
+#include "GUI_App.hpp"
#include "DeviceManager.hpp"
#include "DeviceCore/DevManager.h"
#include "DeviceCore/DevUtil.h"
diff --git a/src/slic3r/GUI/HMS.hpp b/src/slic3r/GUI/HMS.hpp
index d2a87ebf42..c494539b36 100644
--- a/src/slic3r/GUI/HMS.hpp
+++ b/src/slic3r/GUI/HMS.hpp
@@ -1,7 +1,6 @@
#ifndef slic3r_HMS_hpp_
#define slic3r_HMS_hpp_
-#include "GUI_App.hpp"
#include "GUI.hpp"
#include "I18N.hpp"
#include "Widgets/Label.hpp"
@@ -11,7 +10,11 @@
#include "slic3r/Utils/Http.hpp"
#include "libslic3r/Thread.hpp"
#include "nlohmann/json.hpp"
+#include
#include
+#include
+#include
+#include
namespace Slic3r {
@@ -26,12 +29,12 @@ namespace GUI {
class HMSQuery {
protected:
- std::unordered_map m_hms_info_jsons; // key-> device id type, the first three digits of SN number
- std::unordered_map m_hms_action_jsons;// key-> device id type
+ std::unordered_map m_hms_info_jsons; // key-> device id type, the first three digits of SN number
+ std::unordered_map m_hms_action_jsons;// key-> device id type
std::unordered_map m_hms_local_images; // key-> image name
mutable std::mutex m_hms_mutex;
- std::unordered_map m_cloud_hms_last_update_time;
+ std::unordered_map m_cloud_hms_last_update_time;
public:
HMSQuery() { }
@@ -61,18 +64,18 @@ private:
// load hms
void init_hms_info(const std::string& dev_type_id);
void copy_from_data_dir_to_local();
- int download_hms_related(const std::string& hms_type, const std::string& dev_id_type, json* receive_json);
- int load_from_local(const std::string& hms_type, const std::string& dev_id_type, json* receive_json, std::string& version_info);
- int save_to_local(std::string lang, std::string hms_type, std::string dev_id_type, json save_json);
+ int download_hms_related(const std::string& hms_type, const std::string& dev_id_type, nlohmann::json* receive_json);
+ int load_from_local(const std::string& hms_type, const std::string& dev_id_type, nlohmann::json* receive_json, std::string& version_info);
+ int save_to_local(std::string lang, std::string hms_type, std::string dev_id_type, nlohmann::json save_json);
std::string get_hms_file(std::string hms_type, std::string lang = std::string("en"), std::string dev_id_type = "");
// internal query
- string get_dev_id_type(const MachineObject* obj) const;
- wxString _query_hms_msg(const string& dev_id_type, const string& long_error_code, const string& lang_code = std::string("en"));
+ std::string get_dev_id_type(const MachineObject* obj) const;
+ wxString _query_hms_msg(const std::string& dev_id_type, const std::string& long_error_code, const std::string& lang_code = std::string("en"));
- bool _is_internal_error(const string &dev_id_type, const string &long_error_code, const string &lang_code = std::string("en"));
- wxString _query_error_msg(const string& dev_id_type, const std::string& long_error_code, const std::string& lang_code = std::string("en"));
- wxString _query_error_image_action(const string& dev_id_type, const std::string& long_error_code, std::vector& button_action);
+ bool _is_internal_error(const std::string &dev_id_type, const std::string &long_error_code, const std::string &lang_code = std::string("en"));
+ wxString _query_error_msg(const std::string& dev_id_type, const std::string& long_error_code, const std::string& lang_code = std::string("en"));
+ wxString _query_error_image_action(const std::string& dev_id_type, const std::string& long_error_code, std::vector& button_action);
};
int get_hms_info_version(std::string &version);
@@ -85,4 +88,4 @@ std::string get_error_message(int error_code);
}
-#endif
\ No newline at end of file
+#endif
diff --git a/src/slic3r/GUI/MainFrame.cpp b/src/slic3r/GUI/MainFrame.cpp
index 21b6f01893..8aa23b7c35 100644
--- a/src/slic3r/GUI/MainFrame.cpp
+++ b/src/slic3r/GUI/MainFrame.cpp
@@ -497,9 +497,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
});
//BBS
- Bind(EVT_SELECT_TAB, [this](wxCommandEvent&evt) {
- TabPosition pos = (TabPosition)evt.GetInt();
- m_tabpanel->SetSelection(pos);
+ Bind(EVT_SELECT_TAB, [this](wxCommandEvent& evt) {
+ m_tabpanel->SelectPageByName(evt.GetString());
});
Bind(EVT_SYNC_CLOUD_PRESET, &MainFrame::on_select_default_preset, this);
@@ -706,7 +705,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
}
return;}
#endif
- if (evt.CmdDown() && evt.GetKeyCode() == 'R') { if (m_slice_enable) { wxGetApp().plater()->update(true, true); wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE)); this->m_tabpanel->SetSelection(tpPreview); } return; }
+ if (evt.CmdDown() && evt.GetKeyCode() == 'R') { if (m_slice_enable) { wxGetApp().plater()->update(true, true); wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE)); this->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW); } return; }
if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'G') {
m_plater->apply_background_progress();
m_print_enable = get_enable_print_status();
@@ -727,7 +726,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'S') { if (can_save_as()) m_plater->save_project(true); return;}
else if (evt.CmdDown() && evt.GetKeyCode() == 'S') { if (can_save()) m_plater->save_project(); return;}
if (evt.CmdDown() && evt.GetKeyCode() == 'F') {
- if (m_plater && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview)) {
+ if (m_plater && is_prepare_or_preview_tab()) {
m_plater->sidebar().can_search();
}
}
@@ -1011,27 +1010,32 @@ void MainFrame::update_layout()
m_layout = layout;
// From the very beginning the Print settings should be selected
- //m_last_selected_tab = m_layout == ESettingsLayout::Dlg ? 0 : 1;
- m_last_selected_tab = 1;
+ //m_last_selected_tab = m_layout == ESettingsLayout::Dlg ? TAB_ID_HOME : TAB_ID_PREPARE;
+ m_last_selected_tab = TAB_ID_PREPARE;
// Set new settings
switch (m_layout)
{
case ESettingsLayout::Old:
{
-#ifdef SLIC3R_CAD
- m_design_panel->Reparent(m_tabpanel);
- m_tabpanel->InsertPage(tpDesign, m_design_panel, _L("Design"), std::string("tab_design_active"), std::string("tab_design_active"), false);
-#endif
m_plater->Reparent(m_tabpanel);
- m_tabpanel->InsertPage(tp3DEditor, m_plater, _L("Prepare"), std::string("tab_3d_active"), std::string("tab_3d_active"), false);
- m_tabpanel->InsertPage(tpPreview, m_plater, _L("Preview"), std::string("tab_preview_active"), std::string("tab_preview_active"), false);
+ // Right after Home — or first, when there is no Home tab (PositionAfter() would
+ // append instead, and by now the other built-in tabs are already in place).
+ const int home_idx = m_tabpanel->FindPageByName(TAB_ID_HOME);
+ size_t prepare_pos = (home_idx == wxNOT_FOUND) ? 0 : static_cast(home_idx) + 1;
+#ifdef SLIC3R_CAD
+ // Design sits between Home and Prepare, so it goes in first and pushes Prepare along.
+ m_design_panel->Reparent(m_tabpanel);
+ m_tabpanel->InsertPage(prepare_pos++, TAB_ID_DESIGN, m_design_panel, _L("Design"), "tab_design_active");
+#endif
+ m_tabpanel->InsertPage(prepare_pos, TAB_ID_PREPARE, m_plater, _L("Prepare"), "tab_3d_active");
+ m_tabpanel->InsertPage(prepare_pos + 1, TAB_ID_PREVIEW, m_plater, _L("Preview"), "tab_preview_active");
m_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 0);
m_tabpanel->Bind(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, [this](wxCommandEvent& evt)
{
// jump to 3deditor under preview_only mode
- if (evt.GetId() == tp3DEditor){
+ if (evt.GetId() == m_tabpanel->FindPageByName(TAB_ID_PREPARE)) {
Sidebar& sidebar = GUI::wxGetApp().sidebar();
if (sidebar.need_auto_sync_after_connect_printer()) {
sidebar.set_need_auto_sync_after_connect_printer(false);
@@ -1115,6 +1119,9 @@ void MainFrame::update_edge_panels()
void MainFrame::shutdown()
{
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "MainFrame::shutdown enter";
+ if (m_project != nullptr)
+ m_project->shutdown();
+ m_plugin_pages.shutdown();
#ifdef __WXGTK__
// Edge panels are child windows — wxWidgets destroys them automatically.
m_edge_bottom = nullptr;
@@ -1260,15 +1267,14 @@ void MainFrame::init_tabpanel() {
#endif
//BBS
wxWindow* panel = m_tabpanel->GetCurrentPage();
- int sel = m_tabpanel->GetSelection();
//wxString page_text = m_tabpanel->GetPageText(sel);
- m_last_selected_tab = m_tabpanel->GetSelection();
+ m_last_selected_tab = m_tabpanel->GetSelectedPageName();
if (panel == m_plater) {
- if (sel == tp3DEditor) {
+ if (m_last_selected_tab == TAB_ID_PREPARE) {
wxPostEvent(m_plater, SimpleEvent(EVT_GLVIEWTOOLBAR_3D));
m_param_panel->OnActivate();
}
- else if (sel == tpPreview) {
+ else if (m_last_selected_tab == TAB_ID_PREVIEW) {
m_plater->reset_check_status();
if (!m_plater->check_ams_status(m_slice_select == eSliceAll))
return;
@@ -1296,7 +1302,7 @@ void MainFrame::init_tabpanel() {
if (m_design_panel != nullptr && panel != m_design_panel) m_design_panel->on_tab_hidden();
#endif
#ifndef __APPLE__
- if (sel == tp3DEditor) {
+ if (m_last_selected_tab == TAB_ID_PREPARE) {
m_topbar->EnableUndoRedoItems();
}
else {
@@ -1306,34 +1312,16 @@ void MainFrame::init_tabpanel() {
if (panel)
panel->SetFocus();
-
- /*switch (sel) {
- case TabPosition::tpHome:
- show_option(false);
- break;
- case TabPosition::tp3DEditor:
- show_option(true);
- break;
- case TabPosition::tpPreview:
- show_option(true);
- break;
- case TabPosition::tpMonitor:
- show_option(false);
- break;
- default:
- show_option(false);
- break;
- }*/
});
if (wxGetApp().is_editor()) {
m_webview = new WebViewPanel(m_tabpanel);
Bind(EVT_LOAD_URL, [this](wxCommandEvent &evt) {
wxString url = evt.GetString();
- select_tab(MainFrame::tpHome);
+ select_tab(TAB_ID_HOME);
m_webview->load_url(url);
});
- m_tabpanel->AddPage(m_webview, "", "tab_home_active", "tab_home_active", false);
+ m_tabpanel->AddPage(TAB_ID_HOME, m_webview, "", "tab_home_active");
m_param_panel = new ParamsPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
}
@@ -1353,7 +1341,7 @@ void MainFrame::init_tabpanel() {
//BBS add pages
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_monitor->SetBackgroundColour(*wxWHITE);
- m_tabpanel->AddPage(m_monitor, _L("Device"), std::string("tab_monitor_active"), std::string("tab_monitor_active"), false);
+ m_tabpanel->AddPage(TAB_ID_MONITOR, m_monitor, _L("Device"), "tab_monitor_active");
m_printer_view = new PrinterWebView(m_tabpanel);
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent &evt) {
@@ -1368,16 +1356,20 @@ void MainFrame::init_tabpanel() {
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_multi_machine->SetBackgroundColour(*wxWHITE);
// TODO: change the bitmap
- m_tabpanel->AddPage(m_multi_machine, _L("Multi-device"), std::string("tab_multi_active"), std::string("tab_multi_active"), false);
+ m_tabpanel->AddPage(TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active");
}
m_project = new ProjectPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_project->SetBackgroundColour(*wxWHITE);
- m_tabpanel->AddPage(m_project, _L("Project"), std::string("tab_auxiliary_active"), std::string("tab_auxiliary_active"), false);
+ m_tabpanel->AddPage(TAB_ID_PROJECT, m_project, _L("Project"), "tab_auxiliary_active");
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_calibration->SetBackgroundColour(*wxWHITE);
- m_tabpanel->AddPage(m_calibration, _L("Calibration"), std::string("tab_calibration_active"), std::string("tab_calibration_active"), false);
+ m_tabpanel->AddPage(TAB_ID_CALIBRATION, m_calibration, _L("Calibration"), "tab_calibration_active");
+
+ // Plugin pages are appended after the built-in tabs; their ids are namespaced
+ // (plugin..) so they can't collide with the built-in TAB_ID_* constants.
+ m_plugin_pages.initialize(m_tabpanel);
if (m_plater) {
// load initial config
@@ -1399,10 +1391,15 @@ void MainFrame::show_device(bool should_use_native) {
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
- // The web page is appended when printer agents are enabled. Remove that
- // extra page before switching back to the normal native/Web layout.
- if (!use_printer_agents) {
- if ((idx = m_tabpanel->FindPage(m_printer_view)) != wxNOT_FOUND && idx != tpMonitor) {
+ // The web Device page is the extra tab printer-agents mode shows alongside the native one.
+ // Printers that drive the native Bambu device tab have nothing to put in it, so they don't
+ // get it — otherwise a Bambu user sees two Device tabs, one of them permanently empty.
+ const bool want_web_device_tab = use_printer_agents && wxGetApp().preset_bundle != nullptr &&
+ !wxGetApp().preset_bundle->use_bbl_device_tab();
+
+ // Remove the extra page before switching to any layout that shouldn't have it.
+ if (!want_web_device_tab) {
+ if ((idx = m_tabpanel->FindPageByName(TAB_ID_MONITOR_WEB)) != wxNOT_FOUND) {
m_printer_view->Show(false);
m_tabpanel->RemovePage(idx);
}
@@ -1420,8 +1417,8 @@ void MainFrame::show_device(bool should_use_native) {
m_tabpanel->RemovePage(idx);
}
m_monitor->Show(false);
- m_tabpanel->InsertPage(tpMonitor, m_monitor, _L("Device"), std::string("tab_monitor_active"),
- std::string("tab_monitor_active"));
+ m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor,
+ _L("Device"), "tab_monitor_active");
}
if (m_printer_view == nullptr) {
@@ -1442,28 +1439,31 @@ void MainFrame::show_device(bool should_use_native) {
// TODO: change the bitmap
if (m_tabpanel->FindPage(m_multi_machine) == wxNOT_FOUND) {
m_multi_machine->Show(false);
- m_tabpanel->InsertPage(tpMultiDevice, m_multi_machine, _L("Multi-device"), std::string("tab_multi_active"),
- std::string("tab_multi_active"), false);
+ // Past the web Device tab when it is already there, so enabling multi-machine
+ // later can't wedge this page between the two Device tabs.
+ m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR_WEB, TAB_ID_MONITOR}),
+ TAB_ID_MULTI_DEVICE, m_multi_machine, _L("Multi-device"), "tab_multi_active");
}
}
if (!m_calibration) {
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_calibration->SetBackgroundColour(*wxWHITE);
}
- // Calibration is always the last page, so don't use InsertPage here. Otherwise, if multi_machine page is not enabled,
- // the calibration tab won't be properly added as well, due to the TabPosition::tpCalibration no longer matches the real tab position.
if (m_tabpanel->FindPage(m_calibration) == wxNOT_FOUND) {
m_calibration->Show(false);
- m_tabpanel->AddPage(m_calibration, _L("Calibration"), std::string("tab_calibration_active"),
- std::string("tab_calibration_active"), false);
+ m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
+ _L("Calibration"), "tab_calibration_active");
}
- if ((idx = m_tabpanel->FindPage(m_printer_view)) == wxNOT_FOUND) {
- m_printer_view->Show(false);
- m_tabpanel->AddPage(m_printer_view, _L("Device (Web)"), std::string("tab_monitor_active"),
- std::string("tab_monitor_active"), false);
- } else {
- m_tabpanel->SetPageText(idx, _L("Device (Web)"));
+ if (want_web_device_tab) {
+ if ((idx = m_tabpanel->FindPage(m_printer_view)) == wxNOT_FOUND) {
+ m_printer_view->Show(false);
+ // Immediately right of the native Device tab, not at the end of the tab bar.
+ m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MONITOR_WEB,
+ m_printer_view, _L("Device (Web)"), "tab_monitor_active");
+ } else {
+ m_tabpanel->SetPageText(idx, _L("Device (Web)"));
+ }
}
#ifdef _MSW_DARK_MODE
@@ -1471,6 +1471,7 @@ void MainFrame::show_device(bool should_use_native) {
#endif // _MSW_DARK_MODE
fit_tab_labels(); // ORCA on printer change
+ m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above
return;
}
@@ -1492,7 +1493,8 @@ void MainFrame::show_device(bool should_use_native) {
m_monitor->SetBackgroundColour(*wxWHITE);
}
m_monitor->Show(false);
- m_tabpanel->InsertPage(tpMonitor, m_monitor, _L("Device"), std::string("tab_monitor_active"), std::string("tab_monitor_active"));
+ m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor,
+ _L("Device"), "tab_monitor_active");
if (wxGetApp().is_enable_multi_machine()) {
if (!m_multi_machine) {
@@ -1501,18 +1503,18 @@ void MainFrame::show_device(bool should_use_native) {
}
// TODO: change the bitmap
m_multi_machine->Show(false);
- m_tabpanel->InsertPage(tpMultiDevice, m_multi_machine, _L("Multi-device"), std::string("tab_multi_active"),
- std::string("tab_multi_active"), false);
+ m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MULTI_DEVICE, m_multi_machine,
+ _L("Multi-device"), "tab_multi_active");
}
if (!m_calibration) {
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
m_calibration->SetBackgroundColour(*wxWHITE);
}
m_calibration->Show(false);
- // Calibration is always the last page, so don't use InsertPage here. Otherwise, if multi_machine page is not enabled,
- // the calibration tab won't be properly added as well, due to the TabPosition::tpCalibration no longer matches the real tab position.
- m_tabpanel->AddPage(m_calibration, _L("Calibration"), std::string("tab_calibration_active"),
- std::string("tab_calibration_active"), false);
+ // Last of the built-in tabs, but plugin tabs already sit past it — anchor rather than
+ // append, so its position doesn't depend on the relayout() below running afterwards.
+ m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
+ _L("Calibration"), "tab_calibration_active");
#ifdef _MSW_DARK_MODE
wxGetApp().UpdateDarkUIWin(this);
@@ -1545,10 +1547,17 @@ void MainFrame::show_device(bool should_use_native) {
});
}
m_printer_view->Show(false);
- m_tabpanel->InsertPage(tpMonitor, m_printer_view, _L("Device"), std::string("tab_monitor_active"),
- std::string("tab_monitor_active"));
+ m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_printer_view,
+ _L("Device"), "tab_monitor_active");
}
fit_tab_labels(); // ORCA on printer change
+ m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above
+}
+
+bool MainFrame::is_prepare_or_preview_tab() const
+{
+ const wxString tab = m_tabpanel->GetSelectedPageName();
+ return tab == TAB_ID_PREPARE || tab == TAB_ID_PREVIEW;
}
void MainFrame::fit_tab_labels()
@@ -1580,7 +1589,7 @@ void MainFrame::fit_tab_labels()
bool MainFrame::preview_only_hint()
{
if (m_plater && (m_plater->only_gcode_mode() || (m_plater->using_exported_file()))) {
- BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2% in preview mode")%m_tabpanel->GetSelection() %tp3DEditor;
+ BOOST_LOG_TRIVIAL(info) << boost::format("skipped tab switch from %1% to %2% in preview mode")%m_tabpanel->GetSelectedPageName() %wxString(TAB_ID_PREPARE);
ConfirmBeforeSendDialog confirm_dlg(this, wxID_ANY, _L("Warning"));
confirm_dlg.Bind(EVT_SECONDARY_CHECK_CONFIRM, [this](wxCommandEvent& e) {
@@ -1898,22 +1907,22 @@ bool MainFrame::can_clone() const {
bool MainFrame::can_select() const
{
- return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->model().objects.empty();
+ return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->model().objects.empty();
}
bool MainFrame::can_deselect() const
{
- return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->is_selection_empty();
+ return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->is_selection_empty();
}
bool MainFrame::can_delete() const
{
- return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->is_selection_empty();
+ return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->is_selection_empty();
}
bool MainFrame::can_delete_all() const
{
- return (m_plater != nullptr) && (m_tabpanel->GetSelection() == TabPosition::tp3DEditor) && !m_plater->model().objects.empty();
+ return (m_plater != nullptr) && (m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE) && !m_plater->model().objects.empty();
}
bool MainFrame::can_reslice() const
@@ -2022,7 +2031,7 @@ wxBoxSizer* MainFrame::create_side_tools()
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_ALL));
else
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE));
- this->m_tabpanel->SetSelection(tpPreview);
+ this->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW);
}
});
@@ -3169,7 +3178,7 @@ void MainFrame::init_menubar_as_editor()
wxGetApp().app_config->set_bool("auto_perspective", !wxGetApp().app_config->get_bool("auto_perspective"));
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
},
- this, [this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview; },
+ this, [this]() { return is_prepare_or_preview_tab(); },
[this]() { return wxGetApp().app_config->get_bool("auto_perspective"); }, this);
viewMenu->AppendSeparator();
@@ -3178,7 +3187,7 @@ void MainFrame::init_menubar_as_editor()
wxGetApp().toggle_show_gcode_window();
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
},
- this, [this]() { return m_tabpanel->GetSelection() == tpPreview; },
+ this, [this]() { return m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW; },
[this]() { return wxGetApp().show_gcode_window(); }, this);
append_menu_check_item(
@@ -3187,7 +3196,7 @@ void MainFrame::init_menubar_as_editor()
wxGetApp().toggle_show_3d_navigator();
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
},
- this, [this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview; },
+ this, [this]() { return is_prepare_or_preview_tab(); },
[this]() { return wxGetApp().show_3d_navigator(); }, this);
append_menu_check_item(viewMenu, wxID_ANY, _L("Show Gridlines"), _L("Show Gridlines on plate"),
@@ -3195,15 +3204,14 @@ void MainFrame::init_menubar_as_editor()
wxGetApp().toggle_show_plate_gridlines();
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
}, this,
- [this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview; },
+ [this]() { return is_prepare_or_preview_tab(); },
[this]() { return wxGetApp().show_plate_gridlines(); }, this);
append_menu_item(
viewMenu, wxID_ANY, _L("Reset Window Layout"), _L("Reset to default window layout"),
[this](wxCommandEvent&) { m_plater->reset_window_layout(); }, "", this,
[this]() {
- return (m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview) &&
- m_plater->is_sidebar_enabled();
+ return is_prepare_or_preview_tab() && m_plater->is_sidebar_enabled();
},
this);
@@ -3225,7 +3233,7 @@ void MainFrame::init_menubar_as_editor()
wxGetApp().toggle_show_outline();
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
},
- this, [this]() { return m_tabpanel->GetSelection() == TabPosition::tp3DEditor; },
+ this, [this]() { return m_tabpanel->GetSelectedPageName() == TAB_ID_PREPARE; },
[this]() { return wxGetApp().show_outline(); }, this);
/*viewMenu->AppendSeparator();
@@ -4042,13 +4050,16 @@ void MainFrame::select_tab(wxPanel* panel)
wxGetApp().params_dialog()->Popup();
return;
}
+ // Not panel->GetName(): Prepare and Preview share the single m_plater window, so the
+ // window has no one correct name. The slot -> id lookup is the only correct resolution.
int page_idx = m_tabpanel->FindPage(panel);
- if (page_idx == tp3DEditor && m_tabpanel->GetSelection() == tpPreview)
+ wxString page_name = (page_idx == wxNOT_FOUND) ? wxString() : m_tabpanel->GetPageName(static_cast(page_idx));
+ if (page_name == TAB_ID_PREPARE && m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW)
return;
//BBS GUI refactor: remove unused layout new/dlg
/*if (page_idx != wxNOT_FOUND && m_layout == ESettingsLayout::Dlg)
page_idx++;*/
- select_tab(size_t(page_idx));
+ select_tab(page_name);
}
//BBS
@@ -4056,7 +4067,7 @@ void MainFrame::jump_to_monitor(std::string dev_id)
{
if(!m_monitor)
return;
- m_tabpanel->SetSelection(tpMonitor);
+ m_tabpanel->SelectPageByName(TAB_ID_MONITOR);
if (!dev_id.empty()) {
((MonitorPanel*)m_monitor)->select_machine(dev_id);
}
@@ -4066,26 +4077,26 @@ void MainFrame::jump_to_multipage()
{
if(!m_multi_machine)
return;
- m_tabpanel->SetSelection(tpMultiDevice);
+ m_tabpanel->SelectPageByName(TAB_ID_MULTI_DEVICE);
((MultiMachinePage*)m_multi_machine)->jump_to_send_page();
}
//BBS GUI refactor: remove unused layout new/dlg
-void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
+void MainFrame::select_tab(const wxString& id/* = wxString()*/)
{
//bool tabpanel_was_hidden = false;
// Controls on page are created on active page of active tab now.
// We should select/activate tab before its showing to avoid an UI-flickering
- auto select = [this, tab](bool was_hidden) {
- // when tab == -1, it means we should show the last selected tab
+ auto select = [this, id](bool was_hidden) {
+ // when id is empty, it means we should show the last selected tab
//BBS GUI refactor: remove unused layout new/dlg
//size_t new_selection = tab == (size_t)(-1) ? m_last_selected_tab : (m_layout == ESettingsLayout::Dlg && tab != 0) ? tab - 1 : tab;
- size_t new_selection = tab == (size_t)(-1) ? m_last_selected_tab : tab;
+ wxString new_selection = id.empty() ? m_last_selected_tab : id;
- if (m_tabpanel->GetSelection() != (int)new_selection)
- m_tabpanel->SetSelection(new_selection);
+ if (m_tabpanel->GetSelectedPageName() != new_selection)
+ m_tabpanel->SelectPageByName(new_selection);
#ifdef _MSW_DARK_MODE
/*if (wxGetApp().tabs_as_menu()) {
if (Tab* cur_tab = dynamic_cast(m_tabpanel->GetPage(new_selection)))
@@ -4094,10 +4105,12 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
m_plater->get_current_canvas3D()->render();
}*/
#endif
- if (tab == MainFrame::tp3DEditor && m_layout == ESettingsLayout::Old)
+ // Intentionally `id`, not `new_selection`: the fallback-to-last-tab path must not
+ // trigger this render even when the last selected tab was Prepare.
+ if (id == TAB_ID_PREPARE && m_layout == ESettingsLayout::Old)
m_plater->canvas3D()->render();
else if (was_hidden) {
- Tab* cur_tab = dynamic_cast(m_tabpanel->GetPage(new_selection));
+ Tab* cur_tab = dynamic_cast(m_tabpanel->GetPageByName(new_selection));
if (cur_tab)
cur_tab->OnActivate();
}
@@ -4106,10 +4119,10 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
select(false);
}
-void MainFrame::request_select_tab(TabPosition pos)
+void MainFrame::request_select_tab(const wxString& id)
{
wxCommandEvent* evt = new wxCommandEvent(EVT_SELECT_TAB);
- evt->SetInt(pos);
+ evt->SetString(id);
wxQueueEvent(this, evt);
}
@@ -4401,7 +4414,7 @@ void MainFrame::load_printer_url()
}
}
-bool MainFrame::is_printer_view() const { return m_tabpanel->GetSelection() == TabPosition::tpMonitor; }
+bool MainFrame::is_printer_view() const { return m_tabpanel->GetSelectedPageName() == TAB_ID_MONITOR; }
void MainFrame::refresh_plugin_tips()
diff --git a/src/slic3r/GUI/MainFrame.hpp b/src/slic3r/GUI/MainFrame.hpp
index a81bdf6c0c..d6cd5602c3 100644
--- a/src/slic3r/GUI/MainFrame.hpp
+++ b/src/slic3r/GUI/MainFrame.hpp
@@ -35,6 +35,24 @@
#include "PrinterWebView.hpp"
#include "calib_dlg.hpp"
#include "MultiMachinePage.hpp"
+#include "slic3r/plugin/host/PluginPages.hpp"
+
+// Stable identifiers for MainFrame::m_tabpanel's built-in pages. These are
+// names rather than positional indices so optional pages cannot shift them.
+#define TAB_ID_HOME "home"
+#ifdef SLIC3R_CAD
+#define TAB_ID_DESIGN "design"
+#endif
+#define TAB_ID_PREPARE "prepare"
+#define TAB_ID_PREVIEW "preview"
+#define TAB_ID_MONITOR "monitor"
+// Printer-agents mode shows the legacy web page alongside the native Device tab, so it needs an
+// id of its own: sharing TAB_ID_MONITOR makes every name lookup resolve to whichever of the two
+// comes first, which silently defeats PluginPages' selection round-trip across a tab relayout.
+#define TAB_ID_MONITOR_WEB "monitor_web"
+#define TAB_ID_MULTI_DEVICE "multi_device"
+#define TAB_ID_PROJECT "project"
+#define TAB_ID_CALIBRATION "calibration"
#define ENABEL_PRINT_ALL 0
@@ -118,7 +136,7 @@ class MainFrame : public DPIFrame
wxMenuItem* m_menu_item_reslice_now { nullptr };
wxSizer* m_main_sizer{ nullptr };
- size_t m_last_selected_tab;
+ wxString m_last_selected_tab;
std::string get_base_name(const wxString &full_name, const char *extension = nullptr) const;
std::string get_dir_name(const wxString &full_name) const;
@@ -217,24 +235,6 @@ public:
#ifdef __APPLE__
bool get_mac_full_screen() { return m_mac_fullscreen; }
#endif
- //BBS GUI refactor
- // Implicitly numbered: with SLIC3R_CAD off, tpDesign vanishes and every later tab
- // falls back to the index it has upstream. Do not reintroduce explicit values.
- enum TabPosition
- {
- tpHome = 0,
-#ifdef SLIC3R_CAD
- tpDesign,
-#endif
- tp3DEditor,
- tpPreview,
- tpMonitor,
- tpMultiDevice,
- tpProject,
- tpCalibration,
- tpAuxiliary,
- toDebugTool,
- };
//BBS: add slice&&print status update logic
enum SlicePrintEventType
@@ -334,8 +334,8 @@ public:
// When tab == -1, will be selected last selected tab
//BBS: GUI refactor
void select_tab(wxPanel* panel);
- void select_tab(size_t tab = size_t(-1));
- void request_select_tab(TabPosition pos);
+ void select_tab(const wxString& id = wxString());
+ void request_select_tab(const wxString& id);
int get_calibration_curr_tab();
void select_view(const std::string& direction);
// Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig
@@ -368,6 +368,9 @@ public:
//SoftFever
void show_device(bool should_use_native);
void fit_tab_labels(); // ORCA
+ // True while either of the two tabs backed by m_plater is selected.
+ bool is_prepare_or_preview_tab() const;
+ PluginPages& plugin_pages() { return m_plugin_pages; }
PA_Calibration_Dlg* m_pa_calib_dlg{ nullptr };
FlowRateCalibrationDialog* m_flow_rate_calib_dlg{ nullptr };
@@ -396,7 +399,8 @@ public:
CalibrationPanel* m_calibration{ nullptr };
WebViewPanel* m_webview { nullptr };
PrinterWebView* m_printer_view{nullptr};
- wxLogWindow* m_log_window { nullptr };
+ PluginPages m_plugin_pages;
+ wxLogWindow* m_log_window { nullptr };
// BBS
//wxBookCtrlBase* m_tabpanel { nullptr };
Notebook* m_tabpanel{ nullptr };
diff --git a/src/slic3r/GUI/Monitor.cpp b/src/slic3r/GUI/Monitor.cpp
index 9f074d3f93..1a6d969988 100644
--- a/src/slic3r/GUI/Monitor.cpp
+++ b/src/slic3r/GUI/Monitor.cpp
@@ -186,17 +186,17 @@ void MonitorPanel::init_tabpanel()
//m_status_add_machine_panel = new AddMachinePanel(m_tabpanel);
m_status_info_panel = new StatusPanel(m_tabpanel);
- m_tabpanel->AddPage(m_status_info_panel, _L("Status"), "", true);
+ m_tabpanel->AddPage(m_status_info_panel, _L("Status"), true);
m_media_file_panel = new MediaFilePanel(m_tabpanel);
- m_tabpanel->AddPage(m_media_file_panel, _L("Storage"), "", false);
- //m_tabpanel->AddPage(m_media_file_panel, _L("Internal Storage"), "", false);
+ m_tabpanel->AddPage(m_media_file_panel, _L("Storage"), false);
+ //m_tabpanel->AddPage(m_media_file_panel, _L("Internal Storage"), false);
m_upgrade_panel = new UpgradePanel(m_tabpanel);
- m_tabpanel->AddPage(m_upgrade_panel, _L_CONTEXT(L_CONTEXT("Update", "Firmware"), "Firmware"), "", false);
+ m_tabpanel->AddPage(m_upgrade_panel, _L_CONTEXT(L_CONTEXT("Update", "Firmware"), "Firmware"), false);
m_hms_panel = new HMSPanel(m_tabpanel);
- m_tabpanel->AddPage(m_hms_panel, _L("Assistant(HMS)"), "", false);
+ m_tabpanel->AddPage(m_hms_panel, _L("Assistant(HMS)"), false);
std::string network_ver = Slic3r::NetworkAgent::get_version();
if (!network_ver.empty()) {
@@ -413,7 +413,10 @@ void MonitorPanel::update_hms_tag()
bool MonitorPanel::Show(bool show)
{
#ifdef __APPLE__
- wxGetApp().mainframe->SetMinSize(wxGetApp().plater()->GetMinSize());
+ // Notebook::InsertPage() hides every page it appends, so this also runs while MainFrame is
+ // still constructing, before GUI_App::mainframe is assigned. Same guard as Plater::Show().
+ if (wxGetApp().mainframe)
+ wxGetApp().mainframe->SetMinSize(wxGetApp().plater()->GetMinSize());
#endif
NetworkAgent* m_agent = wxGetApp().getAgent();
diff --git a/src/slic3r/GUI/MultiMachinePage.cpp b/src/slic3r/GUI/MultiMachinePage.cpp
index b9b71ad670..88d03007b9 100644
--- a/src/slic3r/GUI/MultiMachinePage.cpp
+++ b/src/slic3r/GUI/MultiMachinePage.cpp
@@ -86,9 +86,9 @@ void MultiMachinePage::init_tabpanel()
m_cloud_task_manager = new CloudTaskManagerPage(m_tabpanel);
m_machine_manager = new MultiMachineManagerPage(m_tabpanel);
- m_tabpanel->AddPage(m_machine_manager, _L("Device"), "", true);
- m_tabpanel->AddPage(m_local_task_manager, _L("Task Sending"), "", false);
- m_tabpanel->AddPage(m_cloud_task_manager, _L("Task Sent"), "", false);
+ m_tabpanel->AddPage(m_machine_manager, _L("Device"), true);
+ m_tabpanel->AddPage(m_local_task_manager, _L("Task Sending"), false);
+ m_tabpanel->AddPage(m_cloud_task_manager, _L("Task Sent"), false);
}
void MultiMachinePage::init_timer()
diff --git a/src/slic3r/GUI/Notebook.cpp b/src/slic3r/GUI/Notebook.cpp
index ceda3fc0d6..673508454a 100644
--- a/src/slic3r/GUI/Notebook.cpp
+++ b/src/slic3r/GUI/Notebook.cpp
@@ -120,11 +120,11 @@ void ButtonsListCtrl::Rescale()
void ButtonsListCtrl::SetSelection(int sel)
{
- if (m_selection == sel)
+ if (m_selection == sel && sel >= 0 && sel < static_cast(m_pageButtons.size()))
return;
// BBS: change button color
wxColour selected_btn_bg("#009688"); // Gradient #009688
- if (m_selection >= 0) {
+ if (m_selection >= 0 && m_selection < static_cast(m_pageButtons.size())) {
StateColor bg_color = StateColor(
std::pair{wxColour(107, 107, 107), (int) StateColor::Hovered},
std::pair{wxColour(59, 68, 70), (int) StateColor::Normal});
@@ -132,9 +132,15 @@ void ButtonsListCtrl::SetSelection(int sel)
StateColor text_color = StateColor(
std::pair{wxColour(254,254, 254), (int) StateColor::Normal}
);
- m_pageButtons[m_selection]->SetSelected(false);
m_pageButtons[m_selection]->SetTextColor(text_color);
}
+
+ if (sel < 0 || sel >= static_cast(m_pageButtons.size())) {
+ m_selection = -1;
+ Refresh();
+ return;
+ }
+
m_selection = sel;
StateColor bg_color = StateColor(
@@ -145,17 +151,19 @@ void ButtonsListCtrl::SetSelection(int sel)
StateColor text_color = StateColor(
std::pair{wxColour(254, 254, 254), (int) StateColor::Normal}
);
- m_pageButtons[m_selection]->SetSelected(true);
m_pageButtons[m_selection]->SetTextColor(text_color);
Refresh();
}
-bool ButtonsListCtrl::InsertPage(size_t n, const wxString &text, bool bSelect /* = false*/, const std::string &bmp_name /* = ""*/, const std::string &inactive_bmp_name)
+bool ButtonsListCtrl::InsertPage(size_t n, const wxString &text, bool bSelect /* = false*/, const std::string &bmp_name /* = ""*/, const wxBitmap &bmp /* = wxNullBitmap */)
{
Button * btn = new Button(this, text.empty() ? text : " " + text, bmp_name, wxNO_BORDER);
btn->SetCornerRadius(0);
+ if (bmp_name.empty() && bmp.IsOk())
+ btn->SetIcon(bmp);
+
int em = em_unit(this);
//BBS set size for button
btn->SetMinSize({(text.empty() ? 40 : 136) * em / 10, 36 * em / 10});
@@ -168,8 +176,6 @@ bool ButtonsListCtrl::InsertPage(size_t n, const wxString &text, bool bSelect /*
StateColor text_color = StateColor(
std::pair{wxColour(254,254, 254), (int) StateColor::Normal});
btn->SetTextColor(text_color);
- btn->SetInactiveIcon(inactive_bmp_name);
- btn->SetSelected(false);
btn->Bind(wxEVT_BUTTON, [this, btn](wxCommandEvent& event) {
if (auto it = std::find(m_pageButtons.begin(), m_pageButtons.end(), btn); it != m_pageButtons.end()) {
auto sel = it - m_pageButtons.begin();
@@ -192,6 +198,14 @@ bool ButtonsListCtrl::InsertPage(size_t n, const wxString &text, bool bSelect /*
void ButtonsListCtrl::RemovePage(size_t n)
{
+ if (n >= m_pageButtons.size())
+ return;
+
+ if (m_selection == static_cast(n))
+ m_selection = -1;
+ else if (m_selection > static_cast(n))
+ --m_selection;
+
Button* btn = m_pageButtons[n];
m_pageButtons.erase(m_pageButtons.begin() + n);
m_pageLabels.erase(m_pageLabels.begin() + n); // ORCA
@@ -240,6 +254,24 @@ wxString ButtonsListCtrl::GetPageText(size_t n) const
return btn->GetLabel();
}
+// ORCA
+void ButtonsListCtrl::SetOverflowButton(wxWindow* button)
+{
+ if (m_overflow_button == button)
+ return;
+
+ if (m_overflow_button != nullptr)
+ m_sizer->Detach(m_overflow_button);
+
+ m_overflow_button = button;
+
+ if (m_overflow_button != nullptr)
+ // Right after the tab buttons (index 0), ahead of any stretch spacer / side_tools.
+ m_sizer->Insert(1, m_overflow_button, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxBOTTOM, m_btn_margin);
+
+ m_sizer->Layout();
+}
+
//#endif // _WIN32
void Notebook::Init()
@@ -253,6 +285,8 @@ void Notebook::Init()
m_showTimeout = m_hideTimeout = 0;
+ m_pageNames.clear();
+
/* On Linux, Gstreamer wxMediaCtrl does not seem to get along well with
* 32-bit X11 visuals (the overlay does not work). Is this a wxWindows
* bug? Is this a Gstreamer bug? No idea, but it is our problem ...
diff --git a/src/slic3r/GUI/Notebook.hpp b/src/slic3r/GUI/Notebook.hpp
index d333956561..4734122b18 100644
--- a/src/slic3r/GUI/Notebook.hpp
+++ b/src/slic3r/GUI/Notebook.hpp
@@ -3,7 +3,11 @@
//#ifdef _WIN32
+#include
+#include
+#include
#include
+#include
#include
class ScalableButton;
@@ -23,13 +27,16 @@ public:
void SetSelection(int sel);
void UpdateMode();
void Rescale();
- bool InsertPage(size_t n, const wxString &text, bool bSelect = false, const std::string &bmp_name = "", const std::string &inactive_bmp_name = "");
+ bool InsertPage(size_t n, const wxString &text, bool bSelect = false, const std::string &bmp_name = "", const wxBitmap &bmp = wxNullBitmap);
void RemovePage(size_t n);
bool SetPageImage(size_t n, const std::string& bmp_name) const;
void SetPageText(size_t n, const wxString& strText);
void SetCompact(size_t n, bool compact); // ORCA
wxString GetPageText(size_t n) const;
wxFlexGridSizer* GetBtnsSizer(){return m_buttons_sizer;}; // ORCA
+ // ORCA: a companion widget shown right after the tab buttons (before any side_tools), e.g.
+ // an overflow indicator. Pass nullptr to remove it; ownership stays with the caller.
+ void SetOverflowButton(wxWindow* button);
private:
wxFlexGridSizer* m_buttons_sizer;
@@ -40,9 +47,10 @@ private:
int m_btn_margin;
int m_line_margin;
std::vector m_pageLabels; // ORCA
+ wxWindow* m_overflow_button{nullptr}; // ORCA
};
-class Notebook: public wxBookCtrlBase
+class Notebook : public wxBookCtrlBase
{
public:
Notebook(wxWindow * parent,
@@ -103,7 +111,7 @@ public:
// by this control) and show it immediately.
bool ShowNewPage(wxWindow * page)
{
- return AddPage(page, wxString(), "", "");
+ return AddPage(page, wxString(), false, NO_IMAGE);
}
@@ -135,51 +143,56 @@ public:
// Implement base class pure virtual methods.
- // adds a new page to the control
- bool AddPage(wxWindow* page,
+ // Page management. Every insertion funnels through the InsertPage() below; `id` is the
+ // stable page name FindPageByName() resolves. Built-in tabs name a resource bitmap,
+ // plugin pages hand over a ready wxBitmap; wx's own imageId overloads carry neither.
+ bool AddPage(const wxString& id,
+ wxWindow* page,
const wxString& text,
- const std::string& bmp_name,
- const std::string& inactive_bmp_name,
+ const std::string& bmp_name = "",
bool bSelect = false)
{
DoInvalidateBestSize();
- return InsertPage(GetPageCount(), page, text, bmp_name, inactive_bmp_name, bSelect);
+ return InsertPage(GetPageCount(), id, page, text, bmp_name, bSelect);
}
- // Page management
- virtual bool InsertPage(size_t n,
- wxWindow * page,
- const wxString & text,
- bool bSelect = false,
- int imageId = NO_IMAGE) override
+ bool AddPage(wxWindow* page, const wxString& text, bool bSelect = false, int imageId = NO_IMAGE) override
{
- if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect, imageId))
+ DoInvalidateBestSize();
+ return InsertPage(GetPageCount(), page, text, bSelect, imageId);
+ }
+
+ bool InsertPage(size_t n,
+ const wxString& id,
+ wxWindow * page,
+ const wxString & text,
+ const std::string& bmp_name = "",
+ bool bSelect = false,
+ const wxBitmap& bmp = wxNullBitmap)
+ {
+ if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect))
return false;
- GetBtnsListCtrl()->InsertPage(n, text, bSelect);
+ m_pageNames.insert(m_pageNames.begin() + n, id);
+ GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name, bmp);
+ // wxBookCtrlBase::InsertPage() only inserts into the page list and sizes the new
+ // page to the current page's rect — it never touches visibility, and a freshly
+ // constructed page defaults to shown. Without this it renders on top of whatever
+ // page is currently selected until the next SetSelection() call hides it.
if (!DoSetSelectionAfterInsertion(n, bSelect))
page->Hide();
return true;
}
- bool InsertPage(size_t n,
- wxWindow * page,
- const wxString & text,
- const std::string& bmp_name = "",
- const std::string& inactive_bmp_name = "",
- bool bSelect = false)
+ virtual bool InsertPage(size_t n,
+ wxWindow * page,
+ const wxString & text,
+ bool bSelect = false,
+ int WXUNUSED(imageId) = NO_IMAGE) override
{
- if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect))
- return false;
-
- GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name, inactive_bmp_name);
-
- if (bSelect)
- SetSelection(n);
-
- return true;
+ return InsertPage(n, wxString(), page, text, "", bSelect);
}
virtual int SetSelection(size_t n) override
@@ -211,8 +224,8 @@ public:
return DoSetSelection(n);
}
- // Neither labels nor images are supported but we still store the labels
- // just in case the user code attaches some importance to them.
+ // Labels are stored by the custom button list; wx's image-list API is unused — tab icons
+ // are set directly on the buttons, either from a resource name or a ready wxBitmap.
virtual bool SetPageText(size_t n, const wxString & strText) override
{
wxCHECK_MSG(n < GetPageCount(), false, wxS("Invalid page"));
@@ -251,7 +264,64 @@ public:
page->SetFocus();
}
+ // The base clears its page list directly instead of calling DoRemovePage() per page,
+ // which would leave m_pageNames behind. No caller today; kept in sync regardless.
+ virtual bool DeleteAllPages() override
+ {
+ m_pageNames.clear();
+ return wxBookCtrlBase::DeleteAllPages();
+ }
+
ButtonsListCtrl* GetBtnsListCtrl() const { return static_cast(m_bookctrl); }
+ void SetOverflowButton(wxWindow* button) { GetBtnsListCtrl()->SetOverflowButton(button); }
+
+ // Insertion index just past the first of `ids` that is present, or the end of the bar
+ // if none is — lets call sites state tab order as "after X" instead of re-deriving it.
+ size_t PositionAfter(std::initializer_list ids) const
+ {
+ for (const char* id : ids)
+ if (const int idx = FindPageByName(id); idx != wxNOT_FOUND)
+ return static_cast(idx) + 1;
+ return GetPageCount();
+ }
+
+ int FindPageByName(const wxString& id) const
+ {
+ if (id.empty())
+ return wxNOT_FOUND;
+ for (size_t i = 0; i < m_pageNames.size(); ++i)
+ if (m_pageNames[i] == id)
+ return static_cast(i);
+ return wxNOT_FOUND;
+ }
+
+ wxWindow* GetPageByName(const wxString& id) const
+ {
+ const int idx = FindPageByName(id);
+ return idx == wxNOT_FOUND ? nullptr : GetPage(static_cast(idx));
+ }
+
+ bool SelectPageByName(const wxString& id)
+ {
+ const int idx = FindPageByName(id);
+ if (idx == wxNOT_FOUND)
+ return false;
+ SetSelection(static_cast(idx));
+ return true;
+ }
+
+ // Inverse of FindPageByName: index -> id. Empty string for an out-of-range
+ // index or a page that was never given an id (e.g. settings Tab pages).
+ wxString GetPageName(size_t n) const
+ {
+ return n < m_pageNames.size() ? m_pageNames[n] : wxString();
+ }
+
+ wxString GetSelectedPageName() const
+ {
+ const int sel = GetSelection();
+ return sel < 0 ? wxString() : GetPageName(static_cast(sel));
+ }
void UpdateMode()
{
@@ -369,6 +439,7 @@ protected:
wxWindow* const win = wxBookCtrlBase::DoRemovePage(page);
if (win)
{
+ m_pageNames.erase(m_pageNames.begin() + page);
GetBtnsListCtrl()->RemovePage(page);
DoSetSelectionAfterRemoval(page);
}
@@ -394,6 +465,8 @@ protected:
private:
void Init();
+ std::vector m_pageNames; // index-parallel to wxBookCtrlBase::m_pages
+
wxShowEffect m_showEffect,
m_hideEffect;
diff --git a/src/slic3r/GUI/NotificationManager.cpp b/src/slic3r/GUI/NotificationManager.cpp
index 8e81f0654c..5e83ad845f 100644
--- a/src/slic3r/GUI/NotificationManager.cpp
+++ b/src/slic3r/GUI/NotificationManager.cpp
@@ -1918,7 +1918,7 @@ void NotificationManager::push_validate_error_notification(StringObjectException
wxGetApp().sidebar().jump_to_option(opt, Preset::TYPE_PRINT, L"");
}
else {
- wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
}
return false;
};
@@ -1985,7 +1985,7 @@ void NotificationManager::push_validate_error_notification(StringObjectException
wxGetApp().sidebar().jump_to_option(opt, opt_type, L"");
}
else {
- wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
}
return false;
};
@@ -2015,7 +2015,7 @@ void NotificationManager::push_slicing_error_notification(const std::string &tex
if (iter != objects.end()) { ovs.push_back({ *iter, nullptr }); }
}
if (!ovs.empty()) {
- wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
wxGetApp().obj_list()->select_items(ovs);
}
return false;
@@ -2046,7 +2046,7 @@ void NotificationManager::push_slicing_warning_notification(const std::string& t
auto& objects = wxGetApp().model().objects;
auto iter = std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; });
if (iter != objects.end()) {
- wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
wxGetApp().obj_list()->select_items({ {*iter, nullptr} });
}
return false;
@@ -2693,7 +2693,7 @@ void NotificationManager::push_slicing_serious_warning_notification(const std::s
if (iter != objects.end()) { ovs.push_back({ *iter, nullptr }); }
}
if (!ovs.empty()) {
- wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
wxGetApp().obj_list()->select_items(ovs);
wxGetApp().obj_list()->update_selections_on_canvas();
}
@@ -2777,7 +2777,7 @@ void NotificationManager::push_slicing_serious_warning_notification(const std::s
}
}
- wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (!sel_items.empty()) {
obj_list->select_items(sel_items);
diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp
index 33dc724149..1cf973370f 100644
--- a/src/slic3r/GUI/Plater.cpp
+++ b/src/slic3r/GUI/Plater.cpp
@@ -5783,6 +5783,8 @@ private:
bool show_warning_dialog { false };
};
+Plater::~Plater() = default;
+
const std::regex Plater::priv::pattern_bundle(".*[.](amf|amf[.]xml|zip[.]amf|3mf)", std::regex::icase);
const std::regex Plater::priv::pattern_3mf(".*3mf", std::regex::icase);
const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::icase);
@@ -5797,7 +5799,7 @@ bool PlaterDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString &fi
#endif // WIN32
m_mainframe.Raise();
- m_mainframe.select_tab(size_t(MainFrame::tp3DEditor));
+ m_mainframe.select_tab(TAB_ID_PREPARE);
if (wxGetApp().is_editor())
m_plater.select_view_3D("3D");
@@ -6579,9 +6581,9 @@ void Plater::priv::select_next_view_3D()
{
if (current_panel == view3D)
- wxGetApp().mainframe->select_tab(size_t(MainFrame::tpPreview));
+ wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
else if (current_panel == preview)
- wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
// else if (current_panel == assemble_view)
// set_current_panel(view3D);
}
@@ -7907,7 +7909,7 @@ std::vector Plater::priv::load_files(const std::vector& input_
q->select_plate(first_plate_index);
//set to 3d tab
q->select_view_3D("Preview");
- wxGetApp().mainframe->select_tab(MainFrame::tpPreview);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
}
else {
//set to 3d tab
@@ -7926,7 +7928,7 @@ std::vector Plater::priv::load_files(const std::vector& input_
else {
//always set to 3D after loading files
q->select_view_3D("3D");
- wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
}
if (load_model) {
@@ -8839,7 +8841,7 @@ void Plater::priv::process_validation_warning(StringObjectException const &warni
}
}
- wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (inst_idx != -1) {
auto* model = wxGetApp().obj_list()->GetModel();
@@ -8868,7 +8870,7 @@ void Plater::priv::process_validation_warning(StringObjectException const &warni
} else {
auto iter = id.id ? std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; }) : objects.end();
if (iter != objects.end()) {
- wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
wxGetApp().obj_list()->select_items({{*iter, nullptr}});
wxGetApp().obj_list()->update_selections_on_canvas();
}
@@ -11252,13 +11254,19 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
}
const int new_sel = e.GetSelection();
- sidebar_layout.show = new_sel == MainFrame::tp3DEditor || new_sel == MainFrame::tpPreview;
+ if (new_sel == wxNOT_FOUND) {
+ // GetPage(new_sel) below needs a valid index.
+ e.Skip();
+ return;
+ }
+ const wxString new_name = main_frame->m_tabpanel->GetPageName(new_sel);
+ sidebar_layout.show = new_name == TAB_ID_PREPARE || new_name == TAB_ID_PREVIEW;
update_sidebar();
int old_sel = e.GetOldSelection();
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
const bool use_native_device_tab = wxGetApp().preset_bundle &&
(wxGetApp().preset_bundle->use_bbl_device_tab() || use_printer_agents);
- if (use_native_device_tab && new_sel == MainFrame::tpMonitor) {
+ if (use_native_device_tab && new_name == TAB_ID_MONITOR) {
// BBL network module is only required for BBL-vendor printers.
// Non-BBL Python plugins (e.g. moonraker) drive the Device tab without it.
if (!use_printer_agents && wxGetApp().preset_bundle->is_bbl_vendor() && !Slic3r::NetworkAgent::is_network_module_loaded()) {
@@ -11270,12 +11278,15 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
}
}
} else {
+ // Pointer test, not a name lookup: in printer-agents mode this page is TAB_ID_MONITOR_WEB
+ // while the native Device tab holds TAB_ID_MONITOR, and in legacy-web mode it holds
+ // TAB_ID_MONITOR itself.
const bool selecting_web_device_tab = main_frame->m_printer_view &&
main_frame->m_tabpanel->GetPage(new_sel) == main_frame->m_printer_view;
if (selecting_web_device_tab) {
// Use the selected discovered machine when the preset has no host.
main_frame->load_printer_url();
- } else if (new_sel == MainFrame::tpMonitor && wxGetApp().preset_bundle != nullptr) {
+ } else if (new_name == TAB_ID_MONITOR && wxGetApp().preset_bundle != nullptr) {
auto cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config;
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
if (main_frame->m_printer_view && url.empty()) {
@@ -12138,7 +12149,7 @@ bool Plater::priv::check_ams_status_impl(bool is_slice_all)
wxPostEvent(q, SimpleEvent(EVT_GLTOOLBAR_SLICE_ALL));
else
wxPostEvent(q, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE));
- wxGetApp().mainframe->m_tabpanel->SetSelection(MainFrame::TabPosition::tpPreview);
+ wxGetApp().mainframe->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW);
}
return false;
}
@@ -12706,7 +12717,7 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name, const UndoRed
ModelWipeTower& tower = model.wipe_tower;
tower.positions[plate_idx] = Vec2d(tower_x_opt->get_at(plate_idx), tower_y_opt->get_at(plate_idx));
- tower.rotation = proj_cfg.opt_float("wipe_tower_rotation_angle");
+ tower.rotation = config.opt_float("wipe_tower_rotation_angle");
}
}
const GLGizmosManager& gizmos = get_current_canvas3D()->get_canvas_type() == GLCanvas3D::CanvasAssembleView ? assemble_view->get_canvas3d()->get_gizmos_manager() : view3D->get_canvas3d()->get_gizmos_manager();
@@ -12816,7 +12827,7 @@ void Plater::priv::undo_redo_to(std::vector::const_iterator
ModelWipeTower& tower = model.wipe_tower;
tower.positions[plate_idx] = Vec2d(tower_x_opt->get_at(plate_idx), tower_y_opt->get_at(plate_idx));
- tower.rotation = proj_cfg.opt_float("wipe_tower_rotation_angle");
+ tower.rotation = config.opt_float("wipe_tower_rotation_angle");
}
}
const int layer_range_idx = it_snapshot->snapshot_data.layer_range_idx;
@@ -13095,7 +13106,7 @@ int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_
get_notification_manager()->clear_all();
if (!silent)
- wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
//get_partplate_list().reinit();
//get_partplate_list().update_slice_context_to_current_plate(p->background_process);
@@ -13244,7 +13255,7 @@ void Plater::load_project(wxString const& filename2,
if (!m_exported_file) {
p->select_view("topfront");
p->camera.requires_zoom_to_plate = REQUIRES_ZOOM_TO_ALL_PLATE;
- wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
}
else {
p->partplate_list.select_plate_view();
@@ -13358,7 +13369,7 @@ void Plater::import_model_id(wxString download_info)
const int max_retries = 3;
/* jump to 3D eidtor */
- wxGetApp().mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
/* prepare progress dialog */
bool cont = true;
@@ -13667,7 +13678,7 @@ void Plater::calib_pa(const Calib_Params& params)
{
const auto calib_pa_name = wxString::Format(L"Pressure Advance Test");
new_project(false, false, calib_pa_name);
- wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
auto print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
auto printer_config = &wxGetApp().preset_bundle->printers.get_edited_preset().config;
print_config->set_key_value("overhang_reverse", new ConfigOptionBool(false));
@@ -14148,7 +14159,7 @@ void Plater::calib_flowrate(bool is_linear, int pass, InfillPattern pattern) {
if (new_project(false, false, calib_name) == wxID_CANCEL)
return;
- wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (is_linear) {
if (pass == 1)
@@ -14185,7 +14196,7 @@ void Plater::calib_temp(const Calib_Params& params) {
const auto calib_temp_name = wxString::Format(L"Nozzle temperature test");
new_project(false, false, calib_temp_name);
- wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (params.mode != CalibMode::Calib_Temp_Tower) return;
if (!add_model(false, Slic3r::resources_dir() + "/calib/temperature_tower/temperature_tower.drc"))
@@ -14265,7 +14276,7 @@ void Plater::calib_max_vol_speed(const Calib_Params& params)
{
const auto calib_vol_speed_name = wxString::Format(L"Max volumetric speed test");
new_project(false, false, calib_vol_speed_name);
- wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (params.mode != CalibMode::Calib_Vol_speed_Tower)
return;
if (!add_model(false, Slic3r::resources_dir() + "/calib/volumetric_speed/SpeedTestStructure.drc"))
@@ -14344,7 +14355,7 @@ void Plater::calib_retraction(const Calib_Params& params)
{
const auto calib_retraction_name = wxString::Format(L"Retraction");
new_project(false, false, calib_retraction_name);
- wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (params.mode != CalibMode::Calib_Retraction_tower)
return;
@@ -14404,7 +14415,7 @@ void Plater::calib_VFA(const Calib_Params& params)
{
const auto calib_vfa_name = wxString::Format(L"VFA test");
new_project(false, false, calib_vfa_name);
- wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (params.mode != CalibMode::Calib_VFA_Tower)
return;
@@ -14487,7 +14498,7 @@ void Plater::calib_input_shaping_freq(const Calib_Params& params)
{
const auto calib_input_shaping_name = wxString::Format(L"Input shaping Frequency test");
new_project(false, false, calib_input_shaping_name);
- wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (params.mode != CalibMode::Calib_Input_shaping_freq)
return;
@@ -14553,7 +14564,7 @@ void Plater::calib_input_shaping_damp(const Calib_Params& params)
{
const auto calib_input_shaping_name = wxString::Format(L"Input shaping Damping test");
new_project(false, false, calib_input_shaping_name);
- wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (params.mode != CalibMode::Calib_Input_shaping_damp)
return;
@@ -14618,7 +14629,7 @@ void Plater::Calib_Cornering(const Calib_Params& params)
{
const auto Calib_Cornering = wxString::Format(L"Cornering test");
new_project(false, false, Calib_Cornering);
- wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
if (params.mode != CalibMode::Calib_Cornering)
return;
@@ -14751,7 +14762,7 @@ void Plater::load_gcode(const wxString& filename)
//p->gcode_result.reset();
//reset_gcode_toolpaths();
p->preview->reload_print(m_only_gcode);
- wxGetApp().mainframe->select_tab(MainFrame::tpPreview);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
p->set_current_panel(p->preview, true);
p->get_current_canvas3D()->render();
//p->notification_manager->bbl_show_plateinfo_notification(into_u8(_L("Preview only mode for gcode file.")));
@@ -15424,7 +15435,7 @@ LoadType determine_load_type(std::string filename, std::string override_setting)
wxGetApp().app_config->set("import_project_action", std::to_string(choice));
// BBS: jump to plater panel
- wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
return load_type;
}
@@ -15653,7 +15664,7 @@ void Plater::reset_with_confirm()
.ShowModal() == wxID_YES) {
reset();
// BBS: jump to plater panel
- wxGetApp().mainframe->select_tab(size_t(0));
+ wxGetApp().mainframe->select_tab(TAB_ID_HOME);
}
}
@@ -17467,7 +17478,7 @@ int Plater::export_config_3mf(int plate_idx, Export3mfProgressFn proFn)
//BBS
void Plater::send_calibration_job_finished(wxCommandEvent & evt)
{
- p->main_frame->request_select_tab(MainFrame::TabPosition::tpCalibration);
+ p->main_frame->request_select_tab(TAB_ID_CALIBRATION);
auto calibration_panel = p->main_frame->m_calibration;
if (calibration_panel) {
auto curr_wizard = static_cast(calibration_panel->get_tabpanel()->GetPage(evt.GetInt()));
@@ -17499,7 +17510,7 @@ void Plater::print_job_finished(wxCommandEvent &evt)
if (!dev) return;
dev->set_selected_machine(evt.GetString().ToStdString());
- p->main_frame->request_select_tab(MainFrame::TabPosition::tpMonitor);
+ p->main_frame->request_select_tab(TAB_ID_MONITOR);
//jump to monitor and select device status panel
MonitorPanel* curr_monitor = p->main_frame->m_monitor;
if(curr_monitor)
@@ -17514,7 +17525,7 @@ void Plater::send_job_finished(wxCommandEvent& evt)
send_gcode_finish(evt.GetString());
p->hide_send_to_printer_dlg();
- //p->main_frame->request_select_tab(MainFrame::TabPosition::tpMonitor);
+ //p->main_frame->request_select_tab(TAB_ID_MONITOR);
////jump to monitor and select device status panel
//MonitorPanel* curr_monitor = p->main_frame->m_monitor;
//if (curr_monitor)
@@ -18408,7 +18419,7 @@ void Plater::pop_warning_and_go_to_device_page(wxString printer_name, PrinterWar
MessageDialog dlg(this, content, title, wxOK | wxFORWARD | wxICON_WARNING, _L("Device Page"));
auto result = dlg.ShowModal();
if (result == wxFORWARD) {
- wxGetApp().mainframe->select_tab(size_t(MainFrame::tpMonitor));
+ wxGetApp().mainframe->select_tab(TAB_ID_MONITOR);
}
}
diff --git a/src/slic3r/GUI/Plater.hpp b/src/slic3r/GUI/Plater.hpp
index 6c3e0ae58a..c49ca9cf75 100644
--- a/src/slic3r/GUI/Plater.hpp
+++ b/src/slic3r/GUI/Plater.hpp
@@ -291,7 +291,7 @@ public:
Plater(const Plater &) = delete;
Plater &operator=(Plater &&) = delete;
Plater &operator=(const Plater &) = delete;
- ~Plater() = default;
+ ~Plater();
bool Show(bool show = true);
@@ -1025,4 +1025,4 @@ wxArrayString get_all_camera_view_type();
} // namespace GUI
} // namespace Slic3r
-#endif
\ No newline at end of file
+#endif
diff --git a/src/slic3r/GUI/PluginWebDialog.cpp b/src/slic3r/GUI/PluginWebDialog.cpp
index 1808f21ce9..d89aac7270 100644
--- a/src/slic3r/GUI/PluginWebDialog.cpp
+++ b/src/slic3r/GUI/PluginWebDialog.cpp
@@ -15,39 +15,6 @@ namespace Slic3r { namespace GUI {
namespace {
-// Low-specificity element defaults (no !important) for UNSTYLED plugin HTML, so a bare
-// plugin page looks native while any CSS the plugin ships still wins. Built on the
-// --orca-* variables the host injects (see WebViewHostDialog); document-start injected
-// AFTER the host contract so the variables are defined (shares the base injector's
-// WebView2 timing guard).
-std::string plugin_defaults_user_script()
-{
- std::string css;
- css += "";
- return WebViewHostDialog::document_start_injector(css, "orca-plugin-defaults", "beforeend");
-}
-
// Injected into the top-level page at document start (before the plugin's own
// scripts). Defines window.orca as the only host surface the page may use. It
// references window.wx lazily (at call time) so it never races the backend's
@@ -129,7 +96,7 @@ PluginWebDialog::PluginWebDialog(wxWindow* parent,
void PluginWebDialog::add_user_scripts()
{
if (wxWebView* wv = browser()) {
- wv->AddUserScript(wxString::FromUTF8(plugin_defaults_user_script()));
+ wv->AddUserScript(wxString::FromUTF8(WebViewHostDialog::plugin_defaults_user_script()));
wv->AddUserScript(ORCA_BRIDGE_JS);
}
}
diff --git a/src/slic3r/GUI/Preferences.cpp b/src/slic3r/GUI/Preferences.cpp
index 4377db5f27..eac957b689 100644
--- a/src/slic3r/GUI/Preferences.cpp
+++ b/src/slic3r/GUI/Preferences.cpp
@@ -1749,11 +1749,26 @@ void PreferencesDialog::create_items()
g_sizer->Add(item_pop_up_filament_map_dialog);
#endif
+ //// GENERAL > Plugins
+ g_sizer->Add(create_item_title(_L("Plugins")), 1, wxEXPAND);
+
+ auto item_plugin_pages_visible_count = create_item_spinctrl(
+ _L("Visible plugin pages"),
+ "",
+ _L("pages"),
+ _L("Number of plugin pages shown as fixed tabs before the remaining pages collapse into a dropdown on the last tab."),
+ SETTING_PLUGIN_PAGES_VISIBLE_COUNT,
+ PLUGIN_PAGES_VISIBLE_COUNT_MIN,
+ PLUGIN_PAGES_VISIBLE_COUNT_MAX,
+ [](int value) { wxGetApp().mainframe->plugin_pages().set_visible_page_count(value); }
+ );
+ g_sizer->Add(item_plugin_pages_visible_count);
+
g_sizer->AddSpacer(FromDIP(10));
sizer_page->Add(g_sizer, 0, wxEXPAND);
//////////////////////////
- //// CONTROL TAB
+ //// CONTROL TAB
/////////////////////////////////////
m_pref_tabs->AppendItem(_L("Control"));
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
diff --git a/src/slic3r/GUI/PresetComboBoxes.cpp b/src/slic3r/GUI/PresetComboBoxes.cpp
index c979fc3212..f78af21a99 100644
--- a/src/slic3r/GUI/PresetComboBoxes.cpp
+++ b/src/slic3r/GUI/PresetComboBoxes.cpp
@@ -1042,7 +1042,7 @@ bool PlaterPresetComboBox::switch_to_tab()
//BBS Select NoteBook Tab params
if (tab->GetParent() == wxGetApp().params_panel())
- wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
+ wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
else {
wxGetApp().params_dialog()->Popup();
tab->OnActivate();
diff --git a/src/slic3r/GUI/PresetComboBoxes.hpp b/src/slic3r/GUI/PresetComboBoxes.hpp
index 53644cecf5..20f75f4616 100644
--- a/src/slic3r/GUI/PresetComboBoxes.hpp
+++ b/src/slic3r/GUI/PresetComboBoxes.hpp
@@ -39,7 +39,7 @@ public:
PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size = wxDefaultSize, PresetBundle* preset_bundle = nullptr);
~PresetComboBox();
- enum LabelItemType {
+ enum LabelItemType : std::size_t {
LABEL_ITEM_PHYSICAL_PRINTER = 0xffffff01,
LABEL_ITEM_PRINTER_MODELS,
LABEL_ITEM_DISABLED,
diff --git a/src/slic3r/GUI/Project.cpp b/src/slic3r/GUI/Project.cpp
index 57410b1202..6d1eb0e180 100644
--- a/src/slic3r/GUI/Project.cpp
+++ b/src/slic3r/GUI/Project.cpp
@@ -74,7 +74,18 @@ ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos,
Fit();
}
-ProjectPanel::~ProjectPanel() {}
+ProjectPanel::~ProjectPanel()
+{
+ shutdown();
+}
+
+void ProjectPanel::shutdown()
+{
+ m_reload_cancel_token->store(true, std::memory_order_release);
+ if (m_reload_task && m_reload_task->joinable())
+ m_reload_task->join();
+ m_reload_task.reset();
+}
// Helper to convert newlines to
static std::string convert_newlines_to_br(const std::string& text) {
@@ -101,7 +112,17 @@ void ProjectPanel::onWebNavigating(wxWebViewEvent& evt)
void ProjectPanel::on_reload(wxCommandEvent& evt)
{
- boost::thread reload = boost::thread([this] {
+ if (wxTheApp == nullptr || wxGetApp().is_closing() ||
+ m_reload_cancel_token->load(std::memory_order_acquire))
+ return;
+
+ if (m_reload_task && m_reload_task->joinable())
+ m_reload_task->join();
+
+ const auto cancel_token = m_reload_cancel_token;
+ m_reload_task = std::make_unique([this, cancel_token] {
+ if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
+ return;
std::string update_type;
std::string license;
std::string model_name;
@@ -115,6 +136,9 @@ void ProjectPanel::on_reload(wxCommandEvent& evt)
std::map> files;
+ if (wxGetApp().plater() == nullptr)
+ return;
+
Model model = wxGetApp().plater()->model();
auto model_info = model.model_info;
@@ -156,7 +180,14 @@ void ProjectPanel::on_reload(wxCommandEvent& evt)
std::string file_path = encode_path(wxGetApp().plater()->model().get_auxiliary_file_temp_path().c_str());
if (!file_path.empty()) {
files = Reload(file_path);
- wxGetApp().CallAfter([this, file_path, files] { m_auxiliary->Reload(file_path, files); });
+ if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
+ return;
+
+ wxGetApp().CallAfter([this, cancel_token, file_path, files] {
+ if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
+ return;
+ m_auxiliary->Reload(file_path, files);
+ });
} else {
clear_model_info();
return;
@@ -215,15 +246,18 @@ void ProjectPanel::on_reload(wxCommandEvent& evt)
json m_Res = json::object();
m_Res["command"] = "show_3mf_info";
- m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id++);
+ m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id.fetch_add(1, std::memory_order_relaxed));
m_Res["model"] = j;
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore));
- if (m_web_init_completed) {
- wxGetApp().CallAfter([this, strJS] {
+ if (m_web_init_completed.load(std::memory_order_acquire) &&
+ !cancel_token->load(std::memory_order_acquire) && wxTheApp != nullptr && !wxGetApp().is_closing()) {
+ wxGetApp().CallAfter([this, cancel_token, strJS] {
+ if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
+ return;
RunScript(strJS.ToStdString());
- });
+ });
}
});
}
@@ -264,7 +298,7 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt)
}
}
else if (strCmd == "request_3mf_info") {
- m_web_init_completed = true;
+ m_web_init_completed.store(true, std::memory_order_release);
}
else if (strCmd == "edit_project_info") {
show_info_editor(true);
@@ -307,13 +341,20 @@ void ProjectPanel::update_model_data()
void ProjectPanel::clear_model_info()
{
+ if (wxTheApp == nullptr || wxGetApp().is_closing() ||
+ m_reload_cancel_token->load(std::memory_order_acquire))
+ return;
+
json m_Res = json::object();
m_Res["command"] = "clear_3mf_info";
- m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id++);
+ m_Res["sequence_id"] = std::to_string(ProjectPanel::m_sequence_id.fetch_add(1, std::memory_order_relaxed));
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore));
- wxGetApp().CallAfter([this, strJS] {
+ const auto cancel_token = m_reload_cancel_token;
+ wxGetApp().CallAfter([this, cancel_token, strJS] {
+ if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
+ return;
RunScript(strJS.ToStdString());
});
}
diff --git a/src/slic3r/GUI/Project.hpp b/src/slic3r/GUI/Project.hpp
index 0071685e7d..a41f76ba7e 100644
--- a/src/slic3r/GUI/Project.hpp
+++ b/src/slic3r/GUI/Project.hpp
@@ -26,9 +26,11 @@
#include "nlohmann/json.hpp"
#include "slic3r/Utils/json_diff.hpp"
+#include
#include