mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 19:01:02 +00:00
Merge branch 'refactor/printer-agent-interface' of https://github.com/OrcaSlicer/OrcaSlicer into refactor/printer-agent-interface
This commit is contained in:
@@ -1,8 +1,12 @@
|
|||||||
name: Check profiles
|
name: Check profiles
|
||||||
on:
|
on:
|
||||||
pull_request:
|
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:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
- release/*
|
||||||
paths:
|
paths:
|
||||||
- 'resources/profiles/**'
|
- 'resources/profiles/**'
|
||||||
- ".github/workflows/check_profiles.yml"
|
- ".github/workflows/check_profiles.yml"
|
||||||
@@ -20,6 +24,8 @@ permissions:
|
|||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
check_profiles:
|
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
|
name: Check profiles
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -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 = '<!-- pr-merge-bot -->';
|
||||||
|
// 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\n<details><summary>Your current grants</summary>\n\n${formatList(grants)}\n\n</details>`;
|
||||||
|
|
||||||
|
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/<Vendor>/` **and** `resources/profiles/<Vendor>.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}`);
|
||||||
|
}
|
||||||
@@ -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.
|
- 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.
|
- 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.
|
- Include targeted tests or documented verification for behavior changes, especially in slicing logic, profiles, formats, and GUI defaults.
|
||||||
|
- For profile changes (`resources/profiles/<Vendor>/**`), check that `version` in the sibling `resources/profiles/<Vendor>.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.
|
- 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
|
## Localization & translations
|
||||||
|
|||||||
@@ -152,7 +152,7 @@ echo on
|
|||||||
set CMAKE_POLICY_VERSION_MINIMUM=3.5
|
set CMAKE_POLICY_VERSION_MINIMUM=3.5
|
||||||
if "%USE_NINJA%"=="1" (
|
if "%USE_NINJA%"=="1" (
|
||||||
cmake .. -G %CMAKE_GENERATOR% -DORCA_TOOLS=ON %SIG_FLAG% -DBUILD_TESTS=%BUILD_TESTS% -DCMAKE_BUILD_TYPE=%build_type%
|
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 (
|
) else (
|
||||||
cmake .. -G %CMAKE_GENERATOR% -A %arch% -DORCA_TOOLS=ON %SIG_FLAG% -DBUILD_TESTS=%BUILD_TESTS% -DCMAKE_BUILD_TYPE=%build_type%
|
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
|
cmake --build . --config %build_type% --target ALL_BUILD -- -m
|
||||||
|
|||||||
Vendored
+98
@@ -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 $<$<BOOL:${TBB_STRICT}>:/W4> $<$<BOOL:${TBB_STRICT}>:/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 $<$<NOT:$<CONFIG:Debug>>:-flto>)
|
||||||
|
else()
|
||||||
|
set(TBB_IPO_COMPILE_FLAGS $<$<NOT:$<CONFIG:Debug>>:/GL>)
|
||||||
|
set(TBB_IPO_LINK_FLAGS $<$<NOT:$<CONFIG:Debug>>:-LTCG> $<$<NOT:$<CONFIG:Debug>>:-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)
|
||||||
Vendored
+5
-1
@@ -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)
|
set(_patch_command ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_LIST_DIR}/GNU.cmake ./cmake/compilers/GNU.cmake)
|
||||||
else()
|
else()
|
||||||
set(_patch_command "")
|
set(_patch_command "")
|
||||||
@@ -13,6 +15,8 @@ orcaslicer_add_cmake_project(
|
|||||||
-DTBB_BUILD_SHARED=OFF
|
-DTBB_BUILD_SHARED=OFF
|
||||||
-DTBB_BUILD_TESTS=OFF
|
-DTBB_BUILD_TESTS=OFF
|
||||||
-DTBB_TEST=OFF
|
-DTBB_TEST=OFF
|
||||||
|
-DTBB_ENABLE_IPO=OFF
|
||||||
|
-DCMAKE_INTERPROCEDURAL_OPTIMIZATION=OFF
|
||||||
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
-DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
||||||
-DCMAKE_DEBUG_POSTFIX=_debug
|
-DCMAKE_DEBUG_POSTFIX=_debug
|
||||||
)
|
)
|
||||||
|
|||||||
+28
@@ -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 _<CONFIG>
|
||||||
|
--
|
||||||
|
2.43.0
|
||||||
Vendored
+1
@@ -28,6 +28,7 @@ orcaslicer_add_cmake_project(
|
|||||||
GIT_SHALLOW ON
|
GIT_SHALLOW ON
|
||||||
GIT_SUBMODULES 3rdparty/catch 3rdparty/pcre 3rdparty/libwebp
|
GIT_SUBMODULES 3rdparty/catch 3rdparty/pcre 3rdparty/libwebp
|
||||||
DEPENDS ${PNG_PKG} ${ZLIB_PKG} ${EXPAT_PKG} ${JPEG_PKG}
|
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
|
CMAKE_ARGS
|
||||||
-DwxBUILD_PRECOMP=ON
|
-DwxBUILD_PRECOMP=ON
|
||||||
${_wx_toolkit}
|
${_wx_toolkit}
|
||||||
|
|||||||
@@ -37,7 +37,11 @@ target_include_directories(Clipper2
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (WIN32)
|
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()
|
else()
|
||||||
target_compile_options(Clipper2 PRIVATE -Wall -Wextra -Wpedantic -Werror)
|
target_compile_options(Clipper2 PRIVATE -Wall -Wextra -Wpedantic -Werror)
|
||||||
target_link_libraries(Clipper2 PUBLIC -lm)
|
target_link_libraries(Clipper2 PUBLIC -lm)
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ add_library(miniz_static STATIC
|
|||||||
|
|
||||||
if(${CMAKE_C_COMPILER_ID} STREQUAL "GNU")
|
if(${CMAKE_C_COMPILER_ID} STREQUAL "GNU")
|
||||||
target_compile_definitions(miniz_static PRIVATE _GNU_SOURCE)
|
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()
|
endif()
|
||||||
|
|
||||||
target_link_libraries(miniz INTERFACE miniz_static)
|
target_link_libraries(miniz INTERFACE miniz_static)
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ msgstr ""
|
|||||||
"Project-Id-Version: Orca Slicer\n"
|
"Project-Id-Version: Orca Slicer\n"
|
||||||
"Report-Msgid-Bugs-To: \n"
|
"Report-Msgid-Bugs-To: \n"
|
||||||
"POT-Creation-Date: 2026-07-29 17:40-0300\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"
|
"Last-Translator: GlauTech\n"
|
||||||
"Language-Team: \n"
|
"Language-Team: \n"
|
||||||
"Language: tr\n"
|
"Language: tr\n"
|
||||||
@@ -738,9 +738,8 @@ msgstr "Sabit adım sürükleme"
|
|||||||
msgid "Context Menu"
|
msgid "Context Menu"
|
||||||
msgstr "Bağlam Menüsü"
|
msgstr "Bağlam Menüsü"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Toggle Auto-Drop"
|
msgid "Toggle Auto-Drop"
|
||||||
msgstr "Otomatik Bırakmayı Aç/Kapat"
|
msgstr "Otomatik düşürmeyi aç / kapat"
|
||||||
|
|
||||||
msgid "Single sided scaling"
|
msgid "Single sided scaling"
|
||||||
msgstr "Tek taraflı ölçekleme"
|
msgstr "Tek taraflı ölçekleme"
|
||||||
@@ -791,9 +790,8 @@ msgstr "Nesne"
|
|||||||
msgid "Part"
|
msgid "Part"
|
||||||
msgstr "Parça"
|
msgstr "Parça"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Relative"
|
msgid "Relative"
|
||||||
msgstr "Göreli"
|
msgstr "Göreceli"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Coordinate system used for transform actions."
|
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!"
|
msgstr "dilimleme işlemi sırasında yeni veya açık proje dosyasına izin verilmez!"
|
||||||
|
|
||||||
msgid "Open Project"
|
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."
|
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."
|
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"
|
msgid "Orca Cube"
|
||||||
msgstr "Orca Küpü"
|
msgstr "Orca Küpü"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "OrcaSliced Combo"
|
msgid "OrcaSliced Combo"
|
||||||
msgstr "OrcaSliced Combo"
|
msgstr "Orca Dilimleme Paketi"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Orca Badge"
|
msgid "Orca Badge"
|
||||||
msgstr "Orca Rozeti"
|
msgstr "Orca Rozeti"
|
||||||
|
|
||||||
msgid "Orca Tolerance Test"
|
msgid "Orca Tolerance Test"
|
||||||
msgstr "Orca tolerans testi"
|
msgstr "Orca Tolerans Testi"
|
||||||
|
|
||||||
msgid "3DBenchy"
|
msgid "3DBenchy"
|
||||||
msgstr "3DBenchy"
|
msgstr "3DBenchy"
|
||||||
@@ -2807,7 +2804,7 @@ msgid "Set as Individual Objects"
|
|||||||
msgstr "Bireysel nesneler olarak ayarla"
|
msgstr "Bireysel nesneler olarak ayarla"
|
||||||
|
|
||||||
msgid "Fill bed with copies"
|
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"
|
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"
|
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"
|
msgid "Printable"
|
||||||
msgstr "Yazdırılabilir"
|
msgstr "Yazdırılabilir"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Auto Drop"
|
msgid "Auto Drop"
|
||||||
msgstr "Otomatik Bırakma"
|
msgstr "Otomatik düşür"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Automatically drops the selected object to the build plate."
|
msgid "Automatically drops the selected object to the build plate."
|
||||||
@@ -2967,7 +2963,7 @@ msgid "Add Models"
|
|||||||
msgstr "Model ekle"
|
msgstr "Model ekle"
|
||||||
|
|
||||||
msgid "Show Labels"
|
msgid "Show Labels"
|
||||||
msgstr "Etiketleri Göster"
|
msgstr "Etiketleri göster"
|
||||||
|
|
||||||
msgid "To Objects"
|
msgid "To Objects"
|
||||||
msgstr "Nesnelere"
|
msgstr "Nesnelere"
|
||||||
@@ -3009,7 +3005,7 @@ msgid "Select all objects on the current plate"
|
|||||||
msgstr "Mevcut plakadaki tüm nesneleri seç"
|
msgstr "Mevcut plakadaki tüm nesneleri seç"
|
||||||
|
|
||||||
msgid "Select All Plates"
|
msgid "Select All Plates"
|
||||||
msgstr "Tüm Plakaları Seç"
|
msgstr "Tüm plakaları seç"
|
||||||
|
|
||||||
msgid "Select all objects on all plates"
|
msgid "Select all objects on all plates"
|
||||||
msgstr "Tüm plakalardaki tüm nesneleri seç"
|
msgstr "Tüm plakalardaki tüm nesneleri seç"
|
||||||
@@ -3045,13 +3041,13 @@ msgid "Remove the selected plate"
|
|||||||
msgstr "Seçilen plakayı kaldır"
|
msgstr "Seçilen plakayı kaldır"
|
||||||
|
|
||||||
msgid "Add instance"
|
msgid "Add instance"
|
||||||
msgstr "Örnek ekle"
|
msgstr "Kopya ekle"
|
||||||
|
|
||||||
msgid "Add one more instance of the selected object"
|
msgid "Add one more instance of the selected object"
|
||||||
msgstr "Seçilen nesnenin bir örneğini daha ekle"
|
msgstr "Seçilen nesnenin bir örneğini daha ekle"
|
||||||
|
|
||||||
msgid "Remove instance"
|
msgid "Remove instance"
|
||||||
msgstr "Örneği kaldır"
|
msgstr "Kopyayı kaldır"
|
||||||
|
|
||||||
msgid "Remove one instance of the selected object"
|
msgid "Remove one instance of the selected object"
|
||||||
msgstr "Seçilen nesnenin bir örneğini kaldır"
|
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"
|
msgstr "Örnek sayısını ayarlayın"
|
||||||
|
|
||||||
msgid "Change the number of instances of the selected object"
|
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"
|
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"
|
msgid "Fill the remaining area of bed with instances of the selected object"
|
||||||
msgstr "Yatağın kalan alanını seçilen nesnenin örnekleriyle doldurun"
|
msgstr "Yatağın kalan alanını seçilen nesnenin örnekleriyle doldurun"
|
||||||
@@ -3075,7 +3071,7 @@ msgid "Simplify Model"
|
|||||||
msgstr "Modeli basitleştir"
|
msgstr "Modeli basitleştir"
|
||||||
|
|
||||||
msgid "Subdivision mesh"
|
msgid "Subdivision mesh"
|
||||||
msgstr "Alt bölüm ağı"
|
msgstr "Poligon artırma"
|
||||||
|
|
||||||
msgid "(Lost color)"
|
msgid "(Lost color)"
|
||||||
msgstr "(Renk kaybı)"
|
msgstr "(Renk kaybı)"
|
||||||
@@ -3090,10 +3086,10 @@ msgid "Edit Process Settings"
|
|||||||
msgstr "İşlem ayarlarını düzenle"
|
msgstr "İşlem ayarlarını düzenle"
|
||||||
|
|
||||||
msgid "Copy Process Settings"
|
msgid "Copy Process Settings"
|
||||||
msgstr "İşlem Ayarlarını Kopyala"
|
msgstr "İşlem ayarlarını kopyala"
|
||||||
|
|
||||||
msgid "Paste Process Settings"
|
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"
|
msgid "Edit print parameters for a single object"
|
||||||
msgstr "Tek bir nesne için yazdırma parametrelerini düzenleme"
|
msgstr "Tek bir nesne için yazdırma parametrelerini düzenleme"
|
||||||
@@ -3478,7 +3474,7 @@ msgid "More"
|
|||||||
msgstr "Daha"
|
msgstr "Daha"
|
||||||
|
|
||||||
msgid "Open Preferences"
|
msgid "Open Preferences"
|
||||||
msgstr "Tercihleri Aç"
|
msgstr "Tercihleri aç"
|
||||||
|
|
||||||
msgid "Open next tip"
|
msgid "Open next tip"
|
||||||
msgstr "Sonraki ipucunu aç"
|
msgstr "Sonraki ipucunu aç"
|
||||||
@@ -5450,10 +5446,10 @@ msgid "Acceleration"
|
|||||||
msgstr "Hızlanma"
|
msgstr "Hızlanma"
|
||||||
|
|
||||||
msgid "Jerk"
|
msgid "Jerk"
|
||||||
msgstr "Jerk"
|
msgstr "Sarsıntı"
|
||||||
|
|
||||||
msgid "Fan Speed"
|
msgid "Fan Speed"
|
||||||
msgstr "Fan hızı"
|
msgstr "Fan Hızı"
|
||||||
|
|
||||||
msgid "Flow"
|
msgid "Flow"
|
||||||
msgstr "Akış"
|
msgstr "Akış"
|
||||||
@@ -5468,7 +5464,7 @@ msgid "Layer Time"
|
|||||||
msgstr "Katman Süresi"
|
msgstr "Katman Süresi"
|
||||||
|
|
||||||
msgid "Layer Time (log)"
|
msgid "Layer Time (log)"
|
||||||
msgstr "Katman Süresi (günlük)"
|
msgstr "Katman Süresi (log)"
|
||||||
|
|
||||||
msgid "Pressure Advance"
|
msgid "Pressure Advance"
|
||||||
msgstr "Basınç İlerlemesi"
|
msgstr "Basınç İlerlemesi"
|
||||||
@@ -5477,10 +5473,10 @@ msgid "Noop"
|
|||||||
msgstr "Hayır"
|
msgstr "Hayır"
|
||||||
|
|
||||||
msgid "Retract"
|
msgid "Retract"
|
||||||
msgstr "Geri Çekme"
|
msgstr "Geri çekme"
|
||||||
|
|
||||||
msgid "Unretract"
|
msgid "Unretract"
|
||||||
msgstr "İleri İtme"
|
msgstr "İleri itme"
|
||||||
|
|
||||||
msgid "Seam"
|
msgid "Seam"
|
||||||
msgstr "Dikiş"
|
msgstr "Dikiş"
|
||||||
@@ -5578,7 +5574,7 @@ msgid "Acceleration: "
|
|||||||
msgstr "İvme: "
|
msgstr "İvme: "
|
||||||
|
|
||||||
msgid "Jerk: "
|
msgid "Jerk: "
|
||||||
msgstr "Jerk: "
|
msgstr "Sarsıntı: "
|
||||||
|
|
||||||
msgid "PA: "
|
msgid "PA: "
|
||||||
msgstr "PA: "
|
msgstr "PA: "
|
||||||
@@ -5608,7 +5604,7 @@ msgid "Actual speed profile"
|
|||||||
msgstr "Gerçek hız profili"
|
msgstr "Gerçek hız profili"
|
||||||
|
|
||||||
msgid "Statistics of All Plates"
|
msgid "Statistics of All Plates"
|
||||||
msgstr "Tüm Plakaların İstatistikleri"
|
msgstr "Tüm plakaların istatistikleri"
|
||||||
|
|
||||||
msgid "Display"
|
msgid "Display"
|
||||||
msgstr "Ekran"
|
msgstr "Ekran"
|
||||||
@@ -5708,7 +5704,7 @@ msgid "Acceleration (mm/s²)"
|
|||||||
msgstr "İvme (mm/s²)"
|
msgstr "İvme (mm/s²)"
|
||||||
|
|
||||||
msgid "Jerk (mm/s)"
|
msgid "Jerk (mm/s)"
|
||||||
msgstr "Jerk (mm/s)"
|
msgstr "Sarsıntı (mm/s)"
|
||||||
|
|
||||||
msgid "Fan speed (%)"
|
msgid "Fan speed (%)"
|
||||||
msgstr "Fan hızı (%)"
|
msgstr "Fan hızı (%)"
|
||||||
@@ -5759,9 +5755,8 @@ msgstr "Normal mod"
|
|||||||
msgid "Total Filament"
|
msgid "Total Filament"
|
||||||
msgstr "Toplam filament"
|
msgstr "Toplam filament"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Model Filament"
|
msgid "Model Filament"
|
||||||
msgstr "Model Filamenti"
|
msgstr "Model filamenti"
|
||||||
|
|
||||||
msgid "Prepare time"
|
msgid "Prepare time"
|
||||||
msgstr "Hazırlık süresi"
|
msgstr "Hazırlık süresi"
|
||||||
@@ -6282,20 +6277,19 @@ msgid "Setup Wizard"
|
|||||||
msgstr "Kurulum sihirbazı"
|
msgstr "Kurulum sihirbazı"
|
||||||
|
|
||||||
msgid "Show Configuration Folder"
|
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"
|
msgid "Troubleshoot Center"
|
||||||
msgstr "Sorun Giderme Merkezi"
|
msgstr "Sorun giderme merkezi"
|
||||||
|
|
||||||
msgid "Open Network Test"
|
msgid "Open Network Test"
|
||||||
msgstr "Ağ Testini Aç"
|
msgstr "Ağ testini aç"
|
||||||
|
|
||||||
msgid "Show Tip of the Day"
|
msgid "Show Tip of the Day"
|
||||||
msgstr "Günün İpucunu Göster"
|
msgstr "Günün ipucunu göster"
|
||||||
|
|
||||||
msgid "Check for Updates"
|
msgid "Check for Updates"
|
||||||
msgstr "Güncellemeleri Kontrol Et"
|
msgstr "Güncellemeleri kontrol et"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "&About %s"
|
msgid "&About %s"
|
||||||
@@ -6349,7 +6343,7 @@ msgid "Recent files"
|
|||||||
msgstr "Son dosyalar"
|
msgstr "Son dosyalar"
|
||||||
|
|
||||||
msgid "Save Project"
|
msgid "Save Project"
|
||||||
msgstr "Projeyi Kaydet"
|
msgstr "Projeyi kaydet"
|
||||||
|
|
||||||
msgid "Save current project to file"
|
msgid "Save current project to file"
|
||||||
msgstr "Mevcut projeyi dosyaya kaydet"
|
msgstr "Mevcut projeyi dosyaya kaydet"
|
||||||
@@ -6415,13 +6409,13 @@ msgid "Export toolpaths as OBJ"
|
|||||||
msgstr "Takımyollarını OBJ olarak dışa aktar"
|
msgstr "Takımyollarını OBJ olarak dışa aktar"
|
||||||
|
|
||||||
msgid "Export Preset Bundle"
|
msgid "Export Preset Bundle"
|
||||||
msgstr "Ön Ayar Paketini Dışa Aktar"
|
msgstr "Ön ayar paketini dışa aktar"
|
||||||
|
|
||||||
msgid "Export current configuration to files"
|
msgid "Export current configuration to files"
|
||||||
msgstr "Geçerli yapılandırmayı dosyalara aktar"
|
msgstr "Geçerli yapılandırmayı dosyalara aktar"
|
||||||
|
|
||||||
msgid "Export"
|
msgid "Export"
|
||||||
msgstr "Dışa Aktar"
|
msgstr "Dışa aktar"
|
||||||
|
|
||||||
msgid "Quit"
|
msgid "Quit"
|
||||||
msgstr "Çıkış"
|
msgstr "Çıkış"
|
||||||
@@ -6478,13 +6472,13 @@ msgid "Deselects all objects"
|
|||||||
msgstr "Tüm nesnelerin seçimini kaldırır"
|
msgstr "Tüm nesnelerin seçimini kaldırır"
|
||||||
|
|
||||||
msgid "Use Perspective View"
|
msgid "Use Perspective View"
|
||||||
msgstr "Perspektif Görünüm"
|
msgstr "Perspektif görünüm"
|
||||||
|
|
||||||
msgid "Use Orthogonal View"
|
msgid "Use Orthogonal View"
|
||||||
msgstr "Ortogonal Görünüm"
|
msgstr "Ortogonal görünüm"
|
||||||
|
|
||||||
msgid "Auto Perspective"
|
msgid "Auto Perspective"
|
||||||
msgstr "Otomatik Perspektif"
|
msgstr "Otomatik perspektif"
|
||||||
|
|
||||||
msgid "Automatically switch between orthographic and perspective when changing from top/bottom/side views."
|
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."
|
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."
|
msgstr "Previce sahnesinde G-kodu penceresini göster."
|
||||||
|
|
||||||
msgid "Show 3D Navigator"
|
msgid "Show 3D Navigator"
|
||||||
msgstr "3D Gezgini Göster"
|
msgstr "3D gezgini göster"
|
||||||
|
|
||||||
msgid "Show 3D navigator in Prepare and Preview scene."
|
msgid "Show 3D navigator in Prepare and Preview scene."
|
||||||
msgstr "Hazırlama ve Önizleme sahnesinde 3D gezgini göster."
|
msgstr "Hazırlama ve Önizleme sahnesinde 3D gezgini göster."
|
||||||
|
|
||||||
msgid "Show Gridlines"
|
msgid "Show Gridlines"
|
||||||
msgstr "Kılavuz Çizgilerini Göster"
|
msgstr "Kılavuz çizgilerini göster"
|
||||||
|
|
||||||
msgid "Show Gridlines on plate"
|
msgid "Show Gridlines on plate"
|
||||||
msgstr "Kılavuz Çizgilerini plaka üzerinde göster"
|
msgstr "Kılavuz Çizgilerini plaka üzerinde göster"
|
||||||
|
|
||||||
msgid "Reset Window Layout"
|
msgid "Reset Window Layout"
|
||||||
msgstr "Pencere Düzenini Sıfırla"
|
msgstr "Pencere düzenini sıfırla"
|
||||||
|
|
||||||
msgid "Reset to default window layout"
|
msgid "Reset to default window layout"
|
||||||
msgstr "Varsayılan pencere düzenine sıfırla"
|
msgstr "Varsayılan pencere düzenine sıfırla"
|
||||||
|
|
||||||
msgid "Show &Labels"
|
msgid "Show &Labels"
|
||||||
msgstr "Etiketleri Göster"
|
msgstr "Etiketleri göster"
|
||||||
|
|
||||||
msgid "Show object labels in 3D scene."
|
msgid "Show object labels in 3D scene."
|
||||||
msgstr "3B sahnede nesne etiketlerini göster."
|
msgstr "3B sahnede nesne etiketlerini göster."
|
||||||
|
|
||||||
msgid "Show &Overhang"
|
msgid "Show &Overhang"
|
||||||
msgstr "Çıkıntıyı Göster"
|
msgstr "Çıkıntıyı göster"
|
||||||
|
|
||||||
msgid "Show object overhang highlight in 3D scene."
|
msgid "Show object overhang highlight in 3D scene."
|
||||||
msgstr "3B sahnede nesne çıkıntısı vurgusunu göster."
|
msgstr "3B sahnede nesne çıkıntısı vurgusunu göster."
|
||||||
|
|
||||||
msgid "Show Selected Outline (beta)"
|
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."
|
msgid "Show outline around selected object in 3D scene."
|
||||||
msgstr "3D sahnede seçilen nesnenin etrafındaki ana hatları göster."
|
msgstr "3D sahnede seçilen nesnenin etrafındaki ana hatları göster."
|
||||||
@@ -6542,13 +6536,11 @@ msgstr "Düzen"
|
|||||||
msgid "View"
|
msgid "View"
|
||||||
msgstr "Görünüm"
|
msgstr "Görünüm"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Preset Bundle"
|
msgid "Preset Bundle"
|
||||||
msgstr "Ön Ayar Paketi"
|
msgstr "Ön ayar paketi"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Sync Presets"
|
msgid "Sync Presets"
|
||||||
msgstr "Ön Ayarları Eşitle"
|
msgstr "Ön ayarları eşitle"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "Pull and apply the latest presets from OrcaCloud"
|
msgid "Pull and apply the latest presets from OrcaCloud"
|
||||||
@@ -6590,10 +6582,10 @@ msgid "Cornering calibration"
|
|||||||
msgstr "Viraj kalibrasyonu"
|
msgstr "Viraj kalibrasyonu"
|
||||||
|
|
||||||
msgid "Input Shaping Frequency"
|
msgid "Input Shaping Frequency"
|
||||||
msgstr "Input shaping Frekansı"
|
msgstr "Input shaping frekansı"
|
||||||
|
|
||||||
msgid "Input Shaping Damping/zeta factor"
|
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"
|
msgid "Input Shaping"
|
||||||
msgstr "Input shaping"
|
msgstr "Input shaping"
|
||||||
@@ -6601,9 +6593,8 @@ msgstr "Input shaping"
|
|||||||
msgid "VFA"
|
msgid "VFA"
|
||||||
msgstr "VFA"
|
msgstr "VFA"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Calibration Guide"
|
msgid "Calibration Guide"
|
||||||
msgstr "Kalibrasyon Kılavuzu"
|
msgstr "Kalibrasyon kılavuzu"
|
||||||
|
|
||||||
msgid "&Open G-code"
|
msgid "&Open G-code"
|
||||||
msgstr "&G kodunu aç"
|
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"
|
msgid "An object with multiple parts was detected"
|
||||||
msgstr "Birden fazla parçaya sahip nesne algılandı"
|
msgstr "Birden fazla parçaya sahip nesne algılandı"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Auto-Drop"
|
msgid "Auto-Drop"
|
||||||
msgstr "Otomatik Bırakma"
|
msgstr "Otomatik düşür"
|
||||||
|
|
||||||
#, c-format, boost-format
|
#, c-format, boost-format
|
||||||
msgid "Connected printer is %s. It must match the project preset for printing.\n"
|
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"
|
msgid "Split to Objects"
|
||||||
msgstr "Nesnelere Ayır"
|
msgstr "Nesnelere Ayır"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Disable Auto-Drop to preserve Z positioning?\n"
|
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
|
# AI Translated
|
||||||
msgid "Object with floating parts was detected"
|
msgid "Object with floating parts was detected"
|
||||||
@@ -8433,7 +8422,7 @@ msgid "Creating a new project"
|
|||||||
msgstr "Yeni bir proje oluşturma"
|
msgstr "Yeni bir proje oluşturma"
|
||||||
|
|
||||||
msgid "Load project"
|
msgid "Load project"
|
||||||
msgstr "Projeyi Aç"
|
msgstr "Projeyi aç"
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Failed to save the project.\n"
|
"Failed to save the project.\n"
|
||||||
@@ -8869,10 +8858,10 @@ msgid "Current Association: "
|
|||||||
msgstr "Mevcut Bağlantı: "
|
msgstr "Mevcut Bağlantı: "
|
||||||
|
|
||||||
msgid "Current Instance"
|
msgid "Current Instance"
|
||||||
msgstr "Mevcut Örnek"
|
msgstr "Mevcut Kopya"
|
||||||
|
|
||||||
msgid "Current Instance Path: "
|
msgid "Current Instance Path: "
|
||||||
msgstr "Mevcut Örnek Yolu: "
|
msgstr "Mevcut Kopya Yolu: "
|
||||||
|
|
||||||
msgid "General"
|
msgid "General"
|
||||||
msgstr "Genel"
|
msgstr "Genel"
|
||||||
@@ -10708,7 +10697,7 @@ msgid "Reserved keywords found"
|
|||||||
msgstr "Ayrılmış anahtar kelimeler bulundu"
|
msgstr "Ayrılmış anahtar kelimeler bulundu"
|
||||||
|
|
||||||
msgid "Setting Overrides"
|
msgid "Setting Overrides"
|
||||||
msgstr "Ayarların Üzerine Yazma"
|
msgstr "Ayarların Üzerine Yaz"
|
||||||
|
|
||||||
msgid "Basic information"
|
msgid "Basic information"
|
||||||
msgstr "Temel Bilgiler"
|
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."
|
msgstr "Elle modda gruplama hatası. Lütfen nozul sayısını denetleyin veya yeniden gruplayın."
|
||||||
|
|
||||||
msgid "Internal Bridge"
|
msgid "Internal Bridge"
|
||||||
msgstr "İç Köprü"
|
msgstr "İç köprü"
|
||||||
|
|
||||||
msgid "undefined error"
|
msgid "undefined error"
|
||||||
msgstr "bilinmeyen hata"
|
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"
|
msgid "External bridge infill direction"
|
||||||
msgstr "Dış köprü dolgu yönü"
|
msgstr "Dış köprü dolgu yönü"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
#, no-c-format, no-boost-format
|
#, no-c-format, no-boost-format
|
||||||
msgid ""
|
msgid ""
|
||||||
"External Bridging angle override.\n"
|
"External Bridging angle override.\n"
|
||||||
@@ -12937,14 +12925,13 @@ msgstr ""
|
|||||||
"Aksi hâlde verilen açı şuna göre kullanılır:\n"
|
"Aksi hâlde verilen açı şuna göre kullanılır:\n"
|
||||||
" - Mutlak koordinatlar\n"
|
" - Mutlak koordinatlar\n"
|
||||||
" - Mutlak koordinatlar + Model dönüşü: Yönleri modele hizala etkinse\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"
|
"\n"
|
||||||
"Sıfır mutlak açı için 180° kullanın."
|
"Sıfır mutlak açı için 180° kullanın."
|
||||||
|
|
||||||
msgid "Internal bridge infill direction"
|
msgid "Internal bridge infill direction"
|
||||||
msgstr "İç köprü dolgu yönü"
|
msgstr "İç köprü dolgu yönü"
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid ""
|
msgid ""
|
||||||
"Internal Bridging angle override.\n"
|
"Internal Bridging angle override.\n"
|
||||||
"If left to zero, the bridging angle will be calculated automatically for each specific bridge.\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"
|
"Aksi hâlde verilen açı şuna göre kullanılır:\n"
|
||||||
" - Mutlak koordinatlar\n"
|
" - Mutlak koordinatlar\n"
|
||||||
" - Mutlak koordinatlar + Model dönüşü: Yönleri modele hizala etkinse\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"
|
"\n"
|
||||||
"Sıfır mutlak açı için 180° kullanın."
|
"Sıfır mutlak açı için 180° kullanın."
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Relative bridge angle"
|
msgid "Relative bridge angle"
|
||||||
msgstr "Göreli köprü açısı"
|
msgstr "Göreceli köprü açısı"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "When enabled, the bridge angle values are added to the automatically calculated bridge direction instead of overriding it."
|
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."
|
"Not: Elde edilen değer ilk katman akış oranından etkilenmez."
|
||||||
|
|
||||||
msgid "Brim follows compensated outline"
|
msgid "Brim follows compensated outline"
|
||||||
msgstr "Kenar telafi edilen taslağı takip ediyor"
|
msgstr "Kenar toleranslı dış sınırı takip etsin"
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -13724,13 +13710,13 @@ msgid ""
|
|||||||
msgstr ""
|
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"
|
"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"
|
"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"
|
"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"
|
"Hepsi: Tüm uygun eğimli yüzeyler için katı dolgu ekleyin\n"
|
||||||
"Varsayılan değer Tümü'dür."
|
"Varsayılan değer Tümü'dür."
|
||||||
|
|
||||||
msgid "Critical Only"
|
msgid "Critical Only"
|
||||||
msgstr "Yalnızca kritik"
|
msgstr "Kritik"
|
||||||
|
|
||||||
msgid "Moderate"
|
msgid "Moderate"
|
||||||
msgstr "Orta"
|
msgstr "Orta"
|
||||||
@@ -14247,7 +14233,7 @@ msgid "By First filament"
|
|||||||
msgstr "İlk filamente göre"
|
msgstr "İlk filamente göre"
|
||||||
|
|
||||||
msgid "By Highest Temp"
|
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."
|
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."
|
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)"
|
msgstr "Marlin Firmware Köşe Sapması (geleneksel XY Sarsıntı ayarının yerini alır)"
|
||||||
|
|
||||||
msgid "Jerk of outer walls."
|
msgid "Jerk of outer walls."
|
||||||
msgstr "Dış duvar JERK değeri."
|
msgstr "Dış duvar sarsıntı değeri."
|
||||||
|
|
||||||
msgid "Jerk of inner walls."
|
msgid "Jerk of inner walls."
|
||||||
msgstr "İç duvarlar JERK değeri."
|
msgstr "İç duvarlar sarsıntı değeri."
|
||||||
|
|
||||||
msgid "Jerk for top surface."
|
msgid "Jerk for top surface."
|
||||||
msgstr "Üst yüzey için JERK değeri."
|
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.)"
|
"If your Marlin 2 printer uses Classic Jerk set this value to 0.)"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Maksimum bağlantı sapması (M205 J, yalnızca Marlin Aygıt Yazılımı için JD > 0 ise geçerlidir)\n"
|
"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"
|
msgid "Minimum speed for extruding"
|
||||||
msgstr "Ekstrüzyon için minimum hız"
|
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."
|
msgstr "Model yüzeyinde destek oluşturmayın, yalnızca baskı plakasında."
|
||||||
|
|
||||||
msgid "Support critical regions only"
|
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."
|
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."
|
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."
|
"To test Classic Jerk, set 'Maximum Junction Deviation' in Motion ability to 0."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
"Marlin 2 Kavşak Sapması tespit edildi:\n"
|
"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 ""
|
msgid ""
|
||||||
"Marlin 2 Classic Jerk detected:\n"
|
"Marlin 2 Classic Jerk detected:\n"
|
||||||
"To test Junction Deviation, set 'Maximum Junction Deviation' in Motion ability to a value > 0."
|
"To test Junction Deviation, set 'Maximum Junction Deviation' in Motion ability to a value > 0."
|
||||||
msgstr ""
|
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."
|
"Kavşak Sapmasını test etmek için Hareket yeteneğinde 'Maksimum Kavşak Sapması'nı > 0 değerine ayarlayın."
|
||||||
|
|
||||||
msgid ""
|
msgid ""
|
||||||
@@ -19538,7 +19524,7 @@ msgid "Start Test Single-Thread"
|
|||||||
msgstr "Tek İş Parçacığı Testini Başlat"
|
msgstr "Tek İş Parçacığı Testini Başlat"
|
||||||
|
|
||||||
msgid "Export Log"
|
msgid "Export Log"
|
||||||
msgstr "Logu Dışa Aktar"
|
msgstr "Logu dışa aktar"
|
||||||
|
|
||||||
msgid "OrcaSlicer Version:"
|
msgid "OrcaSlicer Version:"
|
||||||
msgstr "OrcaSlicer Sürümü:"
|
msgstr "OrcaSlicer Sürümü:"
|
||||||
@@ -20281,9 +20267,8 @@ msgstr "Sistem klasörü silinemedi..."
|
|||||||
msgid "Failed to determine executable path."
|
msgid "Failed to determine executable path."
|
||||||
msgstr "Yürütülebilir dosya yolu belirlenemedi."
|
msgstr "Yürütülebilir dosya yolu belirlenemedi."
|
||||||
|
|
||||||
# AI Translated
|
|
||||||
msgid "Failed to launch a new instance."
|
msgid "Failed to launch a new instance."
|
||||||
msgstr "Yeni bir örnek başlatılamadı."
|
msgstr "Yeni bir kopya başlatılamadı."
|
||||||
|
|
||||||
# AI Translated
|
# AI Translated
|
||||||
msgid "log(s)"
|
msgid "log(s)"
|
||||||
|
|||||||
+21
-8
@@ -256,14 +256,6 @@ if (WIN32)
|
|||||||
VERBATIM
|
VERBATIM
|
||||||
)
|
)
|
||||||
endforeach ()
|
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 ()
|
else ()
|
||||||
file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/resources" WIN_RESOURCES_SYMLINK)
|
file(TO_NATIVE_PATH "${CMAKE_CURRENT_BINARY_DIR}/resources" WIN_RESOURCES_SYMLINK)
|
||||||
add_custom_command(TARGET OrcaSlicer POST_BUILD
|
add_custom_command(TARGET OrcaSlicer POST_BUILD
|
||||||
@@ -279,6 +271,27 @@ if (WIN32)
|
|||||||
COMMENT "Copying Python runtime into the build tree"
|
COMMENT "Copying Python runtime into the build tree"
|
||||||
VERBATIM)
|
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 ()
|
else ()
|
||||||
if (APPLE AND NOT CMAKE_MACOSX_BUNDLE)
|
if (APPLE AND NOT CMAKE_MACOSX_BUNDLE)
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ void CBaseException::OutputString(LPCTSTR lpszFormat, ...)
|
|||||||
//WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), szBuf, _tcslen(szBuf), NULL, NULL);
|
//WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), szBuf, _tcslen(szBuf), NULL, NULL);
|
||||||
|
|
||||||
//output it to the current directory of binary
|
//output it to the current directory of binary
|
||||||
std::string output_str = textconv_helper::T2A_(szBuf);
|
std::string output_str = static_cast<const char*>(textconv_helper::T2A_(szBuf));
|
||||||
*output_file << output_str;
|
*output_file << output_str;
|
||||||
output_file->flush();
|
output_file->flush();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -280,6 +280,9 @@ void AppConfig::set_defaults()
|
|||||||
set(SETTING_OPENGL_FPS_CAP, std::to_string(fps_cap));
|
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())
|
if (get(SETTING_OPENGL_SHOW_FPS_OVERLAY).empty())
|
||||||
set_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY, false);
|
set_bool(SETTING_OPENGL_SHOW_FPS_OVERLAY, false);
|
||||||
|
|
||||||
@@ -1630,6 +1633,22 @@ void AppConfig::set_network_plugin_version(const std::string& version)
|
|||||||
set(SETTING_NETWORK_PLUGIN_VERSION, 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<std::string> AppConfig::get_skipped_network_versions() const
|
std::vector<std::string> AppConfig::get_skipped_network_versions() const
|
||||||
{
|
{
|
||||||
std::vector<std::string> result;
|
std::vector<std::string> result;
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ using namespace nlohmann;
|
|||||||
#define SETTING_OPENGL_PHONG_SSAO "opengl_phong_ssao"
|
#define SETTING_OPENGL_PHONG_SSAO "opengl_phong_ssao"
|
||||||
#define SETTING_OPENGL_PHONG_SMOOTH_NORMALS "opengl_phong_smooth_normals"
|
#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)
|
#if defined(_WIN32) || defined(_WIN64)
|
||||||
#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.09"
|
#define BAMBU_NETWORK_AGENT_VERSION_LEGACY "01.10.01.09"
|
||||||
#else
|
#else
|
||||||
@@ -374,6 +379,10 @@ public:
|
|||||||
std::string get_network_plugin_version() const;
|
std::string get_network_plugin_version() const;
|
||||||
void set_network_plugin_version(const std::string& version);
|
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<std::string> get_skipped_network_versions() const;
|
std::vector<std::string> get_skipped_network_versions() const;
|
||||||
void add_skipped_network_version(const std::string& version);
|
void add_skipped_network_version(const std::string& version);
|
||||||
bool is_network_version_skipped(const std::string& version) const;
|
bool is_network_version_skipped(const std::string& version) const;
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
template BoundingBoxBase<Point, Points>::BoundingBoxBase(const Points &points);
|
template BoundingBoxBase<Point, Points>::BoundingBoxBase(const Points &points);
|
||||||
|
template void BoundingBoxBase<Point, Points>::construct<0, BoundingBox, Points::const_iterator>(BoundingBox&, Points::const_iterator, Points::const_iterator);
|
||||||
|
template void BoundingBoxBase<Point, Points>::construct<1, BoundingBox, Points::const_iterator>(BoundingBox&, Points::const_iterator, Points::const_iterator);
|
||||||
template BoundingBoxBase<Vec2d>::BoundingBoxBase(const std::vector<Vec2d> &points);
|
template BoundingBoxBase<Vec2d>::BoundingBoxBase(const std::vector<Vec2d> &points);
|
||||||
|
|
||||||
template BoundingBox3Base<Vec3d>::BoundingBox3Base(const std::vector<Vec3d> &points);
|
template BoundingBox3Base<Vec3d>::BoundingBox3Base(const std::vector<Vec3d> &points);
|
||||||
|
|||||||
@@ -149,6 +149,8 @@ set(lisbslic3r_sources
|
|||||||
Fill/FillConcentric.hpp
|
Fill/FillConcentric.hpp
|
||||||
Fill/FillConcentricInternal.cpp
|
Fill/FillConcentricInternal.cpp
|
||||||
Fill/FillConcentricInternal.hpp
|
Fill/FillConcentricInternal.hpp
|
||||||
|
Fill/FillCornerSmoothing.cpp
|
||||||
|
Fill/FillCornerSmoothing.hpp
|
||||||
Fill/Fill.cpp
|
Fill/Fill.cpp
|
||||||
Fill/FillCrossHatch.cpp
|
Fill/FillCrossHatch.cpp
|
||||||
Fill/FillCrossHatch.hpp
|
Fill/FillCrossHatch.hpp
|
||||||
|
|||||||
@@ -970,9 +970,9 @@ std::vector<SurfaceFill> group_fills(const Layer &layer, LockRegionParam &lock_p
|
|||||||
region_config.sparse_infill_rotate_template.value);
|
region_config.sparse_infill_rotate_template.value);
|
||||||
params.fixed_angle = !region_config.sparse_infill_rotate_template.value.empty();
|
params.fixed_angle = !region_config.sparse_infill_rotate_template.value.empty();
|
||||||
|
|
||||||
// Orca: special case; apply smoothing factor only for Hilbert Curve sparse infill.
|
// Orca: the smoothing factor only applies to the sparse infill patterns that
|
||||||
// FillHilbertCurve::generate clamps and validates the value itself.
|
// implement it. The fills clamp and validate the value themselves.
|
||||||
if (params.pattern == ipHilbertCurve)
|
if (is_smoothable_infill_pattern(params.pattern, params.multiline))
|
||||||
params.smooth_factor = 0.01 * region_config.sparse_infill_smooth_factor.value;
|
params.smooth_factor = 0.01 * region_config.sparse_infill_smooth_factor.value;
|
||||||
} else {
|
} else {
|
||||||
const bool top_layer_direction_set = surface.is_top() && region_config.top_layer_direction.value >= 0.;
|
const bool top_layer_direction_set = surface.is_top() && region_config.top_layer_direction.value >= 0.;
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "../ShortestPath.hpp"
|
#include "../ShortestPath.hpp"
|
||||||
#include "../Surface.hpp"
|
#include "../Surface.hpp"
|
||||||
#include "FillBase.hpp"
|
#include "FillBase.hpp"
|
||||||
|
#include "FillCornerSmoothing.hpp"
|
||||||
#include "Fill3DHoneycomb.hpp"
|
#include "Fill3DHoneycomb.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
@@ -271,6 +272,9 @@ void Fill3DHoneycomb::_fill_surface_single(
|
|||||||
for (Polyline &pl : polylines){
|
for (Polyline &pl : polylines){
|
||||||
pl.translate(bb.min);
|
pl.translate(bb.min);
|
||||||
pl.simplify(5 * spacing); // simplify to 5x line width
|
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<double>(params.resolution));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply multiline offset if needed
|
// Apply multiline offset if needed
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
#include "Arachne/WallToolPaths.hpp"
|
#include "Arachne/WallToolPaths.hpp"
|
||||||
|
|
||||||
#include "FillConcentric.hpp"
|
#include "FillConcentric.hpp"
|
||||||
|
#include "FillCornerSmoothing.hpp"
|
||||||
#include <libslic3r/ShortestPath.hpp>
|
#include <libslic3r/ShortestPath.hpp>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
@@ -32,12 +33,32 @@ void FillConcentric::_fill_surface_single(
|
|||||||
|
|
||||||
Polygons loops = to_polygons(contracted);
|
Polygons loops = to_polygons(contracted);
|
||||||
|
|
||||||
ExPolygons last { std::move(contracted) };
|
ExPolygons last { contracted };
|
||||||
while (! last.empty()) {
|
while (! last.empty()) {
|
||||||
last = offset2_ex(last, -(distance + min_spacing/2), +min_spacing/2);
|
last = offset2_ex(last, -(distance + min_spacing/2), +min_spacing/2);
|
||||||
append(loops, to_polygons(last));
|
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<double>(params.resolution), 0.5 * distance,
|
||||||
|
corner_stays_inside);
|
||||||
|
|
||||||
// generate paths from the outermost to the innermost, to avoid
|
// generate paths from the outermost to the innermost, to avoid
|
||||||
// adhesion problems of the first central tiny loops
|
// adhesion problems of the first central tiny loops
|
||||||
loops = union_pt_chained_outside_in(loops);
|
loops = union_pt_chained_outside_in(loops);
|
||||||
|
|||||||
@@ -0,0 +1,226 @@
|
|||||||
|
#include <array>
|
||||||
|
|
||||||
|
#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<Vec2d, 6>;
|
||||||
|
|
||||||
|
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<Vec2d> &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<QuinticBezier> 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<QuinticBezier> 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<Vec2d>& 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<Vec2d> &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<double>(), emit);
|
||||||
|
for (const Point &point : points)
|
||||||
|
smoother.push(point.cast<double>(), emit);
|
||||||
|
if (polygon)
|
||||||
|
// Wrap the first vertex around, so that the last one is a corner as well.
|
||||||
|
smoother.push(points.front().cast<double>(), 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
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <functional>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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<bool(const Vec2d &from, const Vec2d &to)>;
|
||||||
|
|
||||||
|
// 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<typename Emit> 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<typename Emit> 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<Vec2d>& 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<Vec2d> m_corner_points;
|
||||||
|
// Cached flattening of the last corner, valid for corners of the same size and turn angle.
|
||||||
|
std::vector<Vec2d> 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
|
||||||
@@ -3,6 +3,7 @@
|
|||||||
#include "../Surface.hpp"
|
#include "../Surface.hpp"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include "FillBase.hpp"
|
#include "FillBase.hpp"
|
||||||
|
#include "FillCornerSmoothing.hpp"
|
||||||
#include "FillCrossHatch.hpp"
|
#include "FillCrossHatch.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
@@ -205,6 +206,9 @@ void FillCrossHatch ::_fill_surface_single(
|
|||||||
// shift the pattern to the actual space
|
// shift the pattern to the actual space
|
||||||
for (Polyline &pl : polylines) { pl.translate(bb.min); }
|
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<double>(params.resolution));
|
||||||
|
|
||||||
// Apply multiline offset if needed
|
// Apply multiline offset if needed
|
||||||
multiline_fill(polylines, params, spacing);
|
multiline_fill(polylines, params, spacing);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "../ShortestPath.hpp"
|
#include "../ShortestPath.hpp"
|
||||||
#include "../Surface.hpp"
|
#include "../Surface.hpp"
|
||||||
|
|
||||||
|
#include "FillCornerSmoothing.hpp"
|
||||||
#include "FillHoneycomb.hpp"
|
#include "FillHoneycomb.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
@@ -70,6 +71,9 @@ void FillHoneycomb::_fill_surface_single(
|
|||||||
}
|
}
|
||||||
p.rotate(-direction.first, m.hex_center);
|
p.rotate(-direction.first, m.hex_center);
|
||||||
p.simplify(5 * spacing); // simplify to 5x line width
|
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<double>(params.resolution));
|
||||||
all_polylines.push_back(p);
|
all_polylines.push_back(p);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "../Print.hpp"
|
#include "../Print.hpp"
|
||||||
#include "../ShortestPath.hpp"
|
#include "../ShortestPath.hpp"
|
||||||
#include "FillBase.hpp"
|
#include "FillBase.hpp"
|
||||||
|
#include "FillCornerSmoothing.hpp"
|
||||||
#include "FillLightning.hpp"
|
#include "FillLightning.hpp"
|
||||||
#include "Lightning/Generator.hpp"
|
#include "Lightning/Generator.hpp"
|
||||||
|
|
||||||
@@ -17,6 +18,19 @@ void Filler::_fill_surface_single(
|
|||||||
const Layer &layer = generator->getTreesForLayer(this->layer_id);
|
const Layer &layer = generator->getTreesForLayer(this->layer_id);
|
||||||
Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled<coord_t>(0.5 * this->spacing - this->overlap));
|
Polylines fill_lines = layer.convertToLines(to_polygons(expolygon), scaled<coord_t>(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<double>(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<double>(params.resolution), max_reach);
|
||||||
|
|
||||||
// Apply multiline offset if needed
|
// Apply multiline offset if needed
|
||||||
multiline_fill(fill_lines, params, spacing);
|
multiline_fill(fill_lines, params, spacing);
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "../ShortestPath.hpp"
|
#include "../ShortestPath.hpp"
|
||||||
#include "../Surface.hpp"
|
#include "../Surface.hpp"
|
||||||
|
|
||||||
|
#include "FillCornerSmoothing.hpp"
|
||||||
#include "FillPlanePath.hpp"
|
#include "FillPlanePath.hpp"
|
||||||
|
|
||||||
namespace Slic3r {
|
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<Vec2d, 6>;
|
// Rounds the corners of the generated path on its way to the infill output.
|
||||||
|
|
||||||
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<Vec2d> &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<QuinticBezier> 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<QuinticBezier> 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());
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename Output>
|
template<typename Output>
|
||||||
static void generate_smooth_hilbert_curve(
|
class SmoothingPolylineOutput
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
// A Hilbert curve is defined on a square grid whose side is a power of two. As in the unsmoothed
|
public:
|
||||||
// generator, expand the larger requested dimension to the next valid Hilbert grid size. The output
|
SmoothingPolylineOutput(Output &output, const double smooth_factor, const double tolerance)
|
||||||
// clipper or the later region intersection removes the padded part of the traversal.
|
: m_output(output), m_smoother(smooth_factor, tolerance) {}
|
||||||
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;
|
|
||||||
|
|
||||||
const size_t point_count = sz * sz;
|
void reserve(size_t n) { m_output.reserve(n); }
|
||||||
output.reserve(point_count);
|
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
|
private:
|
||||||
// if this helper is invoked with an invalid resolution.
|
// The curves of two adjacent corners meet at the midpoint of the segment they share, where they
|
||||||
const double deviation = resolution > 0. && std::isfinite(resolution) ? resolution : EPSILON;
|
// may round to the very same output point. Drop those, they would be zero length extrusions.
|
||||||
// Construct one canonical 90-degree corner from (-corner_distance, 0) to (0, corner_distance).
|
auto emitter()
|
||||||
// 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
|
return [this](const Vec2d &pt) {
|
||||||
// zero, giving G2 joins to both legs. Every Hilbert turn is an oriented copy of this curve, so flatten
|
const Point snapped = m_output.scaled(pt);
|
||||||
// it only once to the requested chordal-deviation tolerance.
|
if (m_has_last_snapped && snapped == m_last_snapped)
|
||||||
const QuinticBezier corner_curve {{
|
return;
|
||||||
{-corner_distance, 0.}, {-0.7 * corner_distance, 0.}, {-0.4 * corner_distance, 0.},
|
m_last_snapped = snapped;
|
||||||
{0., 0.4 * corner_distance}, {0., 0.7 * corner_distance}, {0., corner_distance}
|
m_has_last_snapped = true;
|
||||||
}};
|
m_output.add_point(pt);
|
||||||
std::vector<Vec2d> 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;
|
|
||||||
}
|
}
|
||||||
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<typename GenerateFn>
|
||||||
|
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<std::remove_reference_t<decltype(out)>> smoothing(out, smooth_factor, resolution);
|
||||||
|
generate(smoothing);
|
||||||
|
smoothing.finish();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (output.clips())
|
||||||
|
run(static_cast<InfillPolylineClipper&>(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)
|
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,
|
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 FillParams ¶ms, InfillPolylineOutput &output)
|
||||||
{
|
{
|
||||||
const double smooth_factor = std::isfinite(params.smooth_factor) ?
|
generate_path(output, params, resolution,
|
||||||
std::clamp(params.smooth_factor, 0., 1.) : 0.;
|
[min_x, min_y, max_x, max_y](auto &out) { generate_hilbert_curve(min_x, min_y, max_x, max_y, out); });
|
||||||
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<InfillPolylineClipper&>(output));
|
|
||||||
else
|
|
||||||
generate_smooth_hilbert_curve(min_x, min_y, max_x, max_y, resolution, corner_distance, output);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Output>
|
template<typename Output>
|
||||||
@@ -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);
|
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
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ public:
|
|||||||
void add_point(const Vec2d& pt) { m_out.emplace_back(this->scaled(pt)); }
|
void add_point(const Vec2d& pt) { m_out.emplace_back(this->scaled(pt)); }
|
||||||
Points&& result() { return std::move(m_out); }
|
Points&& result() { return std::move(m_out); }
|
||||||
virtual bool clips() const { return false; }
|
virtual bool clips() const { return false; }
|
||||||
|
// The output grid the generated points are snapped to.
|
||||||
protected:
|
|
||||||
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)) }; }
|
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.
|
// Output polyline.
|
||||||
Points m_out;
|
Points m_out;
|
||||||
|
|
||||||
@@ -93,6 +93,8 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
bool centered() const override { return true; }
|
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, 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
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "../ShortestPath.hpp"
|
#include "../ShortestPath.hpp"
|
||||||
#include "../VariableWidth.hpp"
|
#include "../VariableWidth.hpp"
|
||||||
|
|
||||||
|
#include "FillCornerSmoothing.hpp"
|
||||||
#include "FillRectilinear.hpp"
|
#include "FillRectilinear.hpp"
|
||||||
|
|
||||||
// #define SLIC3R_DEBUG
|
// #define SLIC3R_DEBUG
|
||||||
@@ -3364,6 +3365,10 @@ bool FillRectilinear::fill_surface_trapezoidal(
|
|||||||
for (Polyline &pl : polylines)
|
for (Polyline &pl : polylines)
|
||||||
pl.translate(rotate_vector.second);
|
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<double>(params.resolution));
|
||||||
|
|
||||||
// Apply multiline fill
|
// Apply multiline fill
|
||||||
multiline_fill(polylines, params, spacing);
|
multiline_fill(polylines, params, spacing);
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ static std::vector<std::string> s_project_options {
|
|||||||
"filament_multi_colour",
|
"filament_multi_colour",
|
||||||
"wipe_tower_x",
|
"wipe_tower_x",
|
||||||
"wipe_tower_y",
|
"wipe_tower_y",
|
||||||
"wipe_tower_rotation_angle",
|
|
||||||
"curr_bed_type",
|
"curr_bed_type",
|
||||||
"flush_multiplier",
|
"flush_multiplier",
|
||||||
// Fast-purge mode: project-level purge control, inert at Default.
|
// Fast-purge mode: project-level purge control, inert at Default.
|
||||||
|
|||||||
@@ -3469,9 +3469,8 @@ void PrintConfigDef::init_fff_params()
|
|||||||
def = this->add("sparse_infill_smooth_factor", coPercent);
|
def = this->add("sparse_infill_smooth_factor", coPercent);
|
||||||
def->label = L("Sparse infill smooth factor");
|
def->label = L("Sparse infill smooth factor");
|
||||||
def->category = L("Strength");
|
def->category = L("Strength");
|
||||||
def->tooltip = L("Controls how strongly sparse infill corners are rounded. 0% keeps the original right-angle path, "
|
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. "
|
"while 100% produces the largest possible curves between adjacent infill lines.");
|
||||||
"Currently applies only to the Hilbert Curve.");
|
|
||||||
def->sidetext = "%";
|
def->sidetext = "%";
|
||||||
def->min = 0;
|
def->min = 0;
|
||||||
def->max = 100;
|
def->max = 100;
|
||||||
|
|||||||
@@ -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 {
|
enum class IroningType {
|
||||||
NoIroning,
|
NoIroning,
|
||||||
TopSurfaces,
|
TopSurfaces,
|
||||||
|
|||||||
@@ -620,6 +620,8 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
plugin/host/PluginHostSlicing.cpp
|
plugin/host/PluginHostSlicing.cpp
|
||||||
plugin/host/PluginHostUi.cpp
|
plugin/host/PluginHostUi.cpp
|
||||||
plugin/host/PluginHostUi.hpp
|
plugin/host/PluginHostUi.hpp
|
||||||
|
plugin/host/PluginPages.cpp
|
||||||
|
plugin/host/PluginPages.hpp
|
||||||
plugin/CloudPluginService.cpp
|
plugin/CloudPluginService.cpp
|
||||||
plugin/CloudPluginService.hpp
|
plugin/CloudPluginService.hpp
|
||||||
plugin/PluginFsUtils.cpp
|
plugin/PluginFsUtils.cpp
|
||||||
@@ -640,6 +642,9 @@ set(SLIC3R_GUI_SOURCES
|
|||||||
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp
|
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp
|
||||||
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp
|
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapability.cpp
|
||||||
plugin/pluginTypes/printerAgent/PrinterAgentPluginCapabilityTrampoline.hpp
|
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.hpp
|
||||||
plugin/pluginTypes/script/ScriptPluginCapability.cpp
|
plugin/pluginTypes/script/ScriptPluginCapability.cpp
|
||||||
plugin/pluginTypes/script/ScriptPluginCapabilityTrampoline.hpp
|
plugin/pluginTypes/script/ScriptPluginCapabilityTrampoline.hpp
|
||||||
|
|||||||
@@ -869,11 +869,11 @@ void AuxiliaryPanel::init_tabpanel()
|
|||||||
m_assembly_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::ASSEMBLY_GUIDE);
|
m_assembly_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::ASSEMBLY_GUIDE);
|
||||||
m_others_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::OTHERS);
|
m_others_panel = new AuFolderPanel(m_tabpanel, AuxiliaryFolderType::OTHERS);
|
||||||
|
|
||||||
m_tabpanel->AddPage(m_designer_panel, _L("Basic Info"), "", true);
|
m_tabpanel->AddPage(m_designer_panel, _L("Basic Info"), true);
|
||||||
m_tabpanel->AddPage(m_pictures_panel, _L("Pictures"), "", false);
|
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_bill_of_materials_panel, _L("Bill of Materials"), false);
|
||||||
m_tabpanel->AddPage(m_assembly_panel, _L("Assembly Guide"), "", false);
|
m_tabpanel->AddPage(m_assembly_panel, _L("Assembly Guide"), false);
|
||||||
m_tabpanel->AddPage(m_others_panel, _L("Others"), "", false);
|
m_tabpanel->AddPage(m_others_panel, _L("Others"), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
wxWindow *AuxiliaryPanel::create_side_tools()
|
wxWindow *AuxiliaryPanel::create_side_tools()
|
||||||
|
|||||||
@@ -488,7 +488,6 @@ void CalibrationPanel::init_tabpanel() {
|
|||||||
selected = true;
|
selected = true;
|
||||||
m_tabpanel->AddPage(m_cali_panels[i],
|
m_tabpanel->AddPage(m_cali_panels[i],
|
||||||
get_calibration_type_name(m_cali_panels[i]->get_calibration_mode()),
|
get_calibration_type_name(m_cali_panels[i]->get_calibration_mode()),
|
||||||
"",
|
|
||||||
selected);
|
selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -752,7 +752,7 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig *config, in
|
|||||||
bool has_top_shell = has_top_shell_layers && config->option<ConfigOptionPercent>("top_surface_density")->value > 0;
|
bool has_top_shell = has_top_shell_layers && config->option<ConfigOptionPercent>("top_surface_density")->value > 0;
|
||||||
bool has_bottom_shell = config->opt_int("bottom_shell_layers") > 0;
|
bool has_bottom_shell = config->opt_int("bottom_shell_layers") > 0;
|
||||||
bool has_solid_infill = has_top_shell_layers || has_bottom_shell;
|
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("top_surface_pattern", has_top_shell);
|
||||||
toggle_field("bottom_surface_pattern", has_bottom_shell);
|
toggle_field("bottom_surface_pattern", has_bottom_shell);
|
||||||
toggle_field("top_surface_density", has_top_shell_layers);
|
toggle_field("top_surface_density", has_top_shell_layers);
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ void Downloader::start_download(const std::string& full_url)
|
|||||||
Plater* plater = wxGetApp().plater();
|
Plater* plater = wxGetApp().plater();
|
||||||
|
|
||||||
mainframe->Freeze();
|
mainframe->Freeze();
|
||||||
mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
|
mainframe->select_tab(TAB_ID_PREPARE);
|
||||||
plater->select_view_3D("3D");
|
plater->select_view_3D("3D");
|
||||||
plater->select_view("plate");
|
plater->select_view("plate");
|
||||||
plater->get_current_canvas3D()->zoom_to_bed();
|
plater->get_current_canvas3D()->zoom_to_bed();
|
||||||
|
|||||||
@@ -331,8 +331,10 @@ void Field::PostInitialize()
|
|||||||
}
|
}
|
||||||
default: break;
|
default: break;
|
||||||
}
|
}
|
||||||
if (tab_id >= 0)
|
if (tab_id >= 0) {
|
||||||
wxGetApp().mainframe->select_tab(tab_id);
|
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)
|
if (tab_id > 0)
|
||||||
// tab panel should be focused for correct navigation between tabs
|
// tab panel should be focused for correct navigation between tabs
|
||||||
wxGetApp().tab_panel()->SetFocus();
|
wxGetApp().tab_panel()->SetFocus();
|
||||||
|
|||||||
@@ -2887,7 +2887,7 @@ void GLCanvas3D::reload_scene(bool refresh_immediately, bool force_full_scene_re
|
|||||||
float x = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_x"))->get_at(plate_id);
|
float x = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_x"))->get_at(plate_id);
|
||||||
float y = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_y"))->get_at(plate_id);
|
float y = dynamic_cast<const ConfigOptionFloats*>(proj_cfg.option("wipe_tower_y"))->get_at(plate_id);
|
||||||
float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_tower_width"))->value;
|
float w = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_tower_width"))->value;
|
||||||
float a = dynamic_cast<const ConfigOptionFloat*>(proj_cfg.option("wipe_tower_rotation_angle"))->value;
|
float a = dynamic_cast<const ConfigOptionFloat*>(m_config->option("wipe_tower_rotation_angle"))->value;
|
||||||
// BBS
|
// BBS
|
||||||
float v = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_volume"))->value;
|
float v = dynamic_cast<const ConfigOptionFloat*>(m_config->option("prime_volume"))->value;
|
||||||
Vec3d plate_origin = ppl.get_plate(plate_id)->get_origin();
|
Vec3d plate_origin = ppl.get_plate(plate_id)->get_origin();
|
||||||
@@ -9196,7 +9196,7 @@ void GLCanvas3D::_render_imgui_select_plate_toolbar()
|
|||||||
view3d_canvas->get_gizmos_manager().reset_all_states(); // close all gizmos
|
view3d_canvas->get_gizmos_manager().reset_all_states(); // close all gizmos
|
||||||
view3d_canvas->reload_scene(true);
|
view3d_canvas->reload_scene(true);
|
||||||
}
|
}
|
||||||
app.mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
|
app.mainframe->select_tab(TAB_ID_PREPARE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+31
-17
@@ -813,12 +813,12 @@ void GUI_App::post_init()
|
|||||||
m_open_method = "url";
|
m_open_method = "url";
|
||||||
} else {
|
} else {
|
||||||
if (this->init_params->input_gcode) {
|
if (this->init_params->input_gcode) {
|
||||||
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
mainframe->select_tab(TAB_ID_PREPARE);
|
||||||
plater_->select_view_3D("3D");
|
plater_->select_view_3D("3D");
|
||||||
this->plater()->load_gcode(from_u8(this->init_params->input_files.front()));
|
this->plater()->load_gcode(from_u8(this->init_params->input_files.front()));
|
||||||
m_open_method = "gcode";
|
m_open_method = "gcode";
|
||||||
} else {
|
} else {
|
||||||
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
mainframe->select_tab(TAB_ID_PREPARE);
|
||||||
plater_->select_view_3D("3D");
|
plater_->select_view_3D("3D");
|
||||||
wxArrayString input_files;
|
wxArrayString input_files;
|
||||||
for (auto& file : this->init_params->input_files) {
|
for (auto& file : this->init_params->input_files) {
|
||||||
@@ -852,7 +852,7 @@ void GUI_App::post_init()
|
|||||||
mainframe->Freeze();
|
mainframe->Freeze();
|
||||||
#endif
|
#endif
|
||||||
plater_->canvas3D()->enable_render(false);
|
plater_->canvas3D()->enable_render(false);
|
||||||
mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
mainframe->select_tab(TAB_ID_PREPARE);
|
||||||
plater_->select_view_3D("3D");
|
plater_->select_view_3D("3D");
|
||||||
//BBS init the opengl resource here
|
//BBS init the opengl resource here
|
||||||
if (!plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen() ||
|
if (!plater_->canvas3D()->get_wxglcanvas()->IsShownOnScreen() ||
|
||||||
@@ -890,9 +890,9 @@ void GUI_App::post_init()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (is_editor())
|
if (is_editor())
|
||||||
mainframe->select_tab(size_t(0));
|
mainframe->select_tab(TAB_ID_HOME);
|
||||||
if (app_config->get("default_page") == "1")
|
if (app_config->get("default_page") == "1")
|
||||||
mainframe->select_tab(size_t(1));
|
mainframe->select_tab(TAB_ID_PREPARE);
|
||||||
#ifndef __linux__
|
#ifndef __linux__
|
||||||
mainframe->Thaw();
|
mainframe->Thaw();
|
||||||
#endif
|
#endif
|
||||||
@@ -1829,10 +1829,10 @@ bool GUI_App::hot_reload_network_plugin()
|
|||||||
wxWindowDisabler disabler;
|
wxWindowDisabler disabler;
|
||||||
|
|
||||||
if (mainframe) {
|
if (mainframe) {
|
||||||
int current_tab = mainframe->m_tabpanel->GetSelection();
|
wxString current_tab = mainframe->m_tabpanel->GetSelectedPageName();
|
||||||
if (current_tab == MainFrame::TabPosition::tpMonitor) {
|
if (current_tab == TAB_ID_MONITOR) {
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << ": navigating away from Monitor tab before unload";
|
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_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_load_callback(NetworkAgentFactory::register_python_plugin);
|
||||||
plugin_mgr.subscribe_on_unload_callback(NetworkAgentFactory::deregister_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_load_callback(refresh_printer_agent_dropdown_after_load);
|
||||||
plugin_mgr.subscribe_on_unload_callback(switch_printer_agent_after_unload);
|
plugin_mgr.subscribe_on_unload_callback(switch_printer_agent_after_unload);
|
||||||
plugin_mgr.subscribe_on_capability_load_callback(
|
plugin_mgr.subscribe_on_capability_load_callback(
|
||||||
@@ -2866,11 +2876,15 @@ void GUI_App::init_plugin_gui_wiring()
|
|||||||
if (Plater* plater = wxGetApp().plater())
|
if (Plater* plater = wxGetApp().plater())
|
||||||
plater->revalidate_current_plate_if_plugins_missing();
|
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(
|
plugin_mgr.subscribe_on_capability_unload_callback(
|
||||||
[refresh_plugins_dialog, switch_printer_agent_after_unload](const PluginCapabilityId& capability) {
|
[refresh_plugins_dialog, switch_printer_agent_after_unload](const PluginCapabilityId& capability) {
|
||||||
if (capability.type == PluginCapabilityType::PrinterConnection)
|
if (capability.type == PluginCapabilityType::PrinterConnection)
|
||||||
NetworkAgentFactory::deregister_python_printer_agent(capability.plugin_key, capability.name);
|
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();
|
refresh_plugins_dialog();
|
||||||
switch_printer_agent_after_unload(capability.plugin_key);
|
switch_printer_agent_after_unload(capability.plugin_key);
|
||||||
});
|
});
|
||||||
@@ -3273,15 +3287,12 @@ bool GUI_App::on_init_inner()
|
|||||||
}
|
}
|
||||||
} */
|
} */
|
||||||
|
|
||||||
copy_network_if_available();
|
|
||||||
|
|
||||||
if (scrn) {
|
if (scrn) {
|
||||||
scrn->SetText(_L("Loading Plugins") + dots, 20);
|
scrn->SetText(_L("Loading Plugins") + dots, 20);
|
||||||
wxYield();
|
wxYield();
|
||||||
}
|
}
|
||||||
|
|
||||||
on_init_network();
|
|
||||||
|
|
||||||
// Initialize plugins after network then register on_load callbacks so once the plugin loads finish, it gets registered automatically.
|
// 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,
|
// initialize() also installs the libslic3r hooks (capability resolver,
|
||||||
// slicing-pipeline dispatcher) via plugin_hooks::install() -- no
|
// 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)
|
if (m_agent)
|
||||||
plugin_mgr.set_cloud_agent(std::dynamic_pointer_cast<OrcaCloudServiceAgent>(m_agent->get_cloud_agent()));
|
plugin_mgr.set_cloud_agent(std::dynamic_pointer_cast<OrcaCloudServiceAgent>(m_agent->get_cloud_agent()));
|
||||||
|
|
||||||
@@ -3382,7 +3396,7 @@ bool GUI_App::on_init_inner()
|
|||||||
mainframe = new MainFrame();
|
mainframe = new MainFrame();
|
||||||
// hide settings tabs after first Layout
|
// hide settings tabs after first Layout
|
||||||
if (is_editor()) {
|
if (is_editor()) {
|
||||||
mainframe->select_tab(size_t(0));
|
mainframe->select_tab(TAB_ID_HOME);
|
||||||
}
|
}
|
||||||
|
|
||||||
sidebar().obj_list()->init();
|
sidebar().obj_list()->init();
|
||||||
@@ -4590,7 +4604,7 @@ void GUI_App::recreate_GUI(const wxString &msg_name)
|
|||||||
mainframe = new MainFrame();
|
mainframe = new MainFrame();
|
||||||
if (is_editor())
|
if (is_editor())
|
||||||
// hide settings tabs after first Layout
|
// 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.
|
// Propagate model objects to object list.
|
||||||
sidebar().obj_list()->init();
|
sidebar().obj_list()->init();
|
||||||
//sidebar().aux_list()->init_auxiliary();
|
//sidebar().aux_list()->init_auxiliary();
|
||||||
@@ -9849,7 +9863,7 @@ bool GUI_App::check_url_association(std::wstring url_prefix, std::wstring& reg_b
|
|||||||
{
|
{
|
||||||
reg_bin = L"";
|
reg_bin = L"";
|
||||||
#ifdef WIN32
|
#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()) {
|
if (!key_full.Exists()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -9875,8 +9889,8 @@ void GUI_App::associate_url(std::wstring url_prefix)
|
|||||||
|
|
||||||
wxString key_string = "\"" + wbinary + "\" \"%1\"";
|
wxString key_string = "\"" + wbinary + "\" \"%1\"";
|
||||||
|
|
||||||
wxRegKey key_first(wxRegKey::HKCU, "Software\\Classes\\" + url_prefix);
|
wxRegKey key_first(wxRegKey::HKCU, L"Software\\Classes\\" + url_prefix);
|
||||||
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_first.Exists()) {
|
if (!key_first.Exists()) {
|
||||||
key_first.Create(false);
|
key_first.Create(false);
|
||||||
}
|
}
|
||||||
@@ -9896,7 +9910,7 @@ void GUI_App::disassociate_url(std::wstring url_prefix)
|
|||||||
#ifdef WIN32
|
#ifdef WIN32
|
||||||
if (is_running_in_msix())
|
if (is_running_in_msix())
|
||||||
return;
|
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()) {
|
if (!key_full.Exists()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "HMS.hpp"
|
#include "HMS.hpp"
|
||||||
|
|
||||||
#include "GUI.hpp"
|
#include "GUI.hpp"
|
||||||
|
#include "GUI_App.hpp"
|
||||||
#include "DeviceManager.hpp"
|
#include "DeviceManager.hpp"
|
||||||
#include "DeviceCore/DevManager.h"
|
#include "DeviceCore/DevManager.h"
|
||||||
#include "DeviceCore/DevUtil.h"
|
#include "DeviceCore/DevUtil.h"
|
||||||
|
|||||||
+16
-13
@@ -1,7 +1,6 @@
|
|||||||
#ifndef slic3r_HMS_hpp_
|
#ifndef slic3r_HMS_hpp_
|
||||||
#define slic3r_HMS_hpp_
|
#define slic3r_HMS_hpp_
|
||||||
|
|
||||||
#include "GUI_App.hpp"
|
|
||||||
#include "GUI.hpp"
|
#include "GUI.hpp"
|
||||||
#include "I18N.hpp"
|
#include "I18N.hpp"
|
||||||
#include "Widgets/Label.hpp"
|
#include "Widgets/Label.hpp"
|
||||||
@@ -11,7 +10,11 @@
|
|||||||
#include "slic3r/Utils/Http.hpp"
|
#include "slic3r/Utils/Http.hpp"
|
||||||
#include "libslic3r/Thread.hpp"
|
#include "libslic3r/Thread.hpp"
|
||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
|
#include <ctime>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
@@ -26,12 +29,12 @@ namespace GUI {
|
|||||||
class HMSQuery {
|
class HMSQuery {
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::unordered_map<string, json> m_hms_info_jsons; // key-> device id type, the first three digits of SN number
|
std::unordered_map<std::string, nlohmann::json> m_hms_info_jsons; // key-> device id type, the first three digits of SN number
|
||||||
std::unordered_map<string, json> m_hms_action_jsons;// key-> device id type
|
std::unordered_map<std::string, nlohmann::json> m_hms_action_jsons;// key-> device id type
|
||||||
std::unordered_map<wxString, wxImage> m_hms_local_images; // key-> image name
|
std::unordered_map<wxString, wxImage> m_hms_local_images; // key-> image name
|
||||||
mutable std::mutex m_hms_mutex;
|
mutable std::mutex m_hms_mutex;
|
||||||
|
|
||||||
std::unordered_map<string, time_t> m_cloud_hms_last_update_time;
|
std::unordered_map<std::string, std::time_t> m_cloud_hms_last_update_time;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
HMSQuery() { }
|
HMSQuery() { }
|
||||||
@@ -61,18 +64,18 @@ private:
|
|||||||
// load hms
|
// load hms
|
||||||
void init_hms_info(const std::string& dev_type_id);
|
void init_hms_info(const std::string& dev_type_id);
|
||||||
void copy_from_data_dir_to_local();
|
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 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, json* receive_json, std::string& version_info);
|
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, json save_json);
|
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 = "");
|
std::string get_hms_file(std::string hms_type, std::string lang = std::string("en"), std::string dev_id_type = "");
|
||||||
|
|
||||||
// internal query
|
// internal query
|
||||||
string get_dev_id_type(const MachineObject* obj) const;
|
std::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"));
|
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"));
|
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 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 string& dev_id_type, const std::string& long_error_code, std::vector<int>& button_action);
|
wxString _query_error_image_action(const std::string& dev_id_type, const std::string& long_error_code, std::vector<int>& button_action);
|
||||||
};
|
};
|
||||||
|
|
||||||
int get_hms_info_version(std::string &version);
|
int get_hms_info_version(std::string &version);
|
||||||
@@ -85,4 +88,4 @@ std::string get_error_message(int error_code);
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+106
-94
@@ -493,9 +493,8 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
|||||||
});
|
});
|
||||||
|
|
||||||
//BBS
|
//BBS
|
||||||
Bind(EVT_SELECT_TAB, [this](wxCommandEvent&evt) {
|
Bind(EVT_SELECT_TAB, [this](wxCommandEvent& evt) {
|
||||||
TabPosition pos = (TabPosition)evt.GetInt();
|
m_tabpanel->SelectPageByName(evt.GetString());
|
||||||
m_tabpanel->SetSelection(pos);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Bind(EVT_SYNC_CLOUD_PRESET, &MainFrame::on_select_default_preset, this);
|
Bind(EVT_SYNC_CLOUD_PRESET, &MainFrame::on_select_default_preset, this);
|
||||||
@@ -702,7 +701,7 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, BORDERLESS_FRAME_
|
|||||||
}
|
}
|
||||||
return;}
|
return;}
|
||||||
#endif
|
#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') {
|
if (evt.CmdDown() && evt.ShiftDown() && evt.GetKeyCode() == 'G') {
|
||||||
m_plater->apply_background_progress();
|
m_plater->apply_background_progress();
|
||||||
m_print_enable = get_enable_print_status();
|
m_print_enable = get_enable_print_status();
|
||||||
@@ -723,7 +722,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;}
|
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;}
|
else if (evt.CmdDown() && evt.GetKeyCode() == 'S') { if (can_save()) m_plater->save_project(); return;}
|
||||||
if (evt.CmdDown() && evt.GetKeyCode() == 'F') {
|
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();
|
m_plater->sidebar().can_search();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1007,8 +1006,8 @@ void MainFrame::update_layout()
|
|||||||
m_layout = layout;
|
m_layout = layout;
|
||||||
|
|
||||||
// From the very beginning the Print settings should be selected
|
// From the very beginning the Print settings should be selected
|
||||||
//m_last_selected_tab = m_layout == ESettingsLayout::Dlg ? 0 : 1;
|
//m_last_selected_tab = m_layout == ESettingsLayout::Dlg ? TAB_ID_HOME : TAB_ID_PREPARE;
|
||||||
m_last_selected_tab = 1;
|
m_last_selected_tab = TAB_ID_PREPARE;
|
||||||
|
|
||||||
// Set new settings
|
// Set new settings
|
||||||
switch (m_layout)
|
switch (m_layout)
|
||||||
@@ -1016,14 +1015,18 @@ void MainFrame::update_layout()
|
|||||||
case ESettingsLayout::Old:
|
case ESettingsLayout::Old:
|
||||||
{
|
{
|
||||||
m_plater->Reparent(m_tabpanel);
|
m_plater->Reparent(m_tabpanel);
|
||||||
m_tabpanel->InsertPage(tp3DEditor, m_plater, _L("Prepare"), std::string("tab_3d_active"), std::string("tab_3d_active"), false);
|
// Right after Home — or first, when there is no Home tab (PositionAfter() would
|
||||||
m_tabpanel->InsertPage(tpPreview, m_plater, _L("Preview"), std::string("tab_preview_active"), std::string("tab_preview_active"), false);
|
// append instead, and by now the other built-in tabs are already in place).
|
||||||
|
const int home_idx = m_tabpanel->FindPageByName(TAB_ID_HOME);
|
||||||
|
const size_t prepare_pos = (home_idx == wxNOT_FOUND) ? 0 : static_cast<size_t>(home_idx) + 1;
|
||||||
|
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_main_sizer->Add(m_tabpanel, 1, wxEXPAND | wxTOP, 0);
|
||||||
|
|
||||||
m_tabpanel->Bind(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, [this](wxCommandEvent& evt)
|
m_tabpanel->Bind(wxCUSTOMEVT_NOTEBOOK_SEL_CHANGED, [this](wxCommandEvent& evt)
|
||||||
{
|
{
|
||||||
// jump to 3deditor under preview_only mode
|
// 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();
|
Sidebar& sidebar = GUI::wxGetApp().sidebar();
|
||||||
if (sidebar.need_auto_sync_after_connect_printer()) {
|
if (sidebar.need_auto_sync_after_connect_printer()) {
|
||||||
sidebar.set_need_auto_sync_after_connect_printer(false);
|
sidebar.set_need_auto_sync_after_connect_printer(false);
|
||||||
@@ -1107,6 +1110,9 @@ void MainFrame::update_edge_panels()
|
|||||||
void MainFrame::shutdown()
|
void MainFrame::shutdown()
|
||||||
{
|
{
|
||||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "MainFrame::shutdown enter";
|
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "MainFrame::shutdown enter";
|
||||||
|
if (m_project != nullptr)
|
||||||
|
m_project->shutdown();
|
||||||
|
m_plugin_pages.shutdown();
|
||||||
#ifdef __WXGTK__
|
#ifdef __WXGTK__
|
||||||
// Edge panels are child windows — wxWidgets destroys them automatically.
|
// Edge panels are child windows — wxWidgets destroys them automatically.
|
||||||
m_edge_bottom = nullptr;
|
m_edge_bottom = nullptr;
|
||||||
@@ -1252,15 +1258,14 @@ void MainFrame::init_tabpanel() {
|
|||||||
#endif
|
#endif
|
||||||
//BBS
|
//BBS
|
||||||
wxWindow* panel = m_tabpanel->GetCurrentPage();
|
wxWindow* panel = m_tabpanel->GetCurrentPage();
|
||||||
int sel = m_tabpanel->GetSelection();
|
|
||||||
//wxString page_text = m_tabpanel->GetPageText(sel);
|
//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 (panel == m_plater) {
|
||||||
if (sel == tp3DEditor) {
|
if (m_last_selected_tab == TAB_ID_PREPARE) {
|
||||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLVIEWTOOLBAR_3D));
|
wxPostEvent(m_plater, SimpleEvent(EVT_GLVIEWTOOLBAR_3D));
|
||||||
m_param_panel->OnActivate();
|
m_param_panel->OnActivate();
|
||||||
}
|
}
|
||||||
else if (sel == tpPreview) {
|
else if (m_last_selected_tab == TAB_ID_PREVIEW) {
|
||||||
m_plater->reset_check_status();
|
m_plater->reset_check_status();
|
||||||
if (!m_plater->check_ams_status(m_slice_select == eSliceAll))
|
if (!m_plater->check_ams_status(m_slice_select == eSliceAll))
|
||||||
return;
|
return;
|
||||||
@@ -1275,7 +1280,7 @@ void MainFrame::init_tabpanel() {
|
|||||||
//monitor
|
//monitor
|
||||||
}
|
}
|
||||||
#ifndef __APPLE__
|
#ifndef __APPLE__
|
||||||
if (sel == tp3DEditor) {
|
if (m_last_selected_tab == TAB_ID_PREPARE) {
|
||||||
m_topbar->EnableUndoRedoItems();
|
m_topbar->EnableUndoRedoItems();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1285,34 +1290,16 @@ void MainFrame::init_tabpanel() {
|
|||||||
|
|
||||||
if (panel)
|
if (panel)
|
||||||
panel->SetFocus();
|
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()) {
|
if (wxGetApp().is_editor()) {
|
||||||
m_webview = new WebViewPanel(m_tabpanel);
|
m_webview = new WebViewPanel(m_tabpanel);
|
||||||
Bind(EVT_LOAD_URL, [this](wxCommandEvent &evt) {
|
Bind(EVT_LOAD_URL, [this](wxCommandEvent &evt) {
|
||||||
wxString url = evt.GetString();
|
wxString url = evt.GetString();
|
||||||
select_tab(MainFrame::tpHome);
|
select_tab(TAB_ID_HOME);
|
||||||
m_webview->load_url(url);
|
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);
|
m_param_panel = new ParamsPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxBK_LEFT | wxTAB_TRAVERSAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1327,7 +1314,7 @@ void MainFrame::init_tabpanel() {
|
|||||||
//BBS add pages
|
//BBS add pages
|
||||||
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
m_monitor = new MonitorPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||||
m_monitor->SetBackgroundColour(*wxWHITE);
|
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);
|
m_printer_view = new PrinterWebView(m_tabpanel);
|
||||||
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent &evt) {
|
Bind(EVT_LOAD_PRINTER_URL, [this](LoadPrinterViewEvent &evt) {
|
||||||
@@ -1342,16 +1329,20 @@ void MainFrame::init_tabpanel() {
|
|||||||
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
m_multi_machine = new MultiMachinePage(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||||
m_multi_machine->SetBackgroundColour(*wxWHITE);
|
m_multi_machine->SetBackgroundColour(*wxWHITE);
|
||||||
// TODO: change the bitmap
|
// 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 = new ProjectPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||||
m_project->SetBackgroundColour(*wxWHITE);
|
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 = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||||
m_calibration->SetBackgroundColour(*wxWHITE);
|
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.<plugin_key>.<name>) so they can't collide with the built-in TAB_ID_* constants.
|
||||||
|
m_plugin_pages.initialize(m_tabpanel);
|
||||||
|
|
||||||
if (m_plater) {
|
if (m_plater) {
|
||||||
// load initial config
|
// load initial config
|
||||||
@@ -1373,10 +1364,15 @@ void MainFrame::show_device(bool should_use_native) {
|
|||||||
|
|
||||||
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
|
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
|
// The web Device page is the extra tab printer-agents mode shows alongside the native one.
|
||||||
// extra page before switching back to the normal native/Web layout.
|
// Printers that drive the native Bambu device tab have nothing to put in it, so they don't
|
||||||
if (!use_printer_agents) {
|
// get it — otherwise a Bambu user sees two Device tabs, one of them permanently empty.
|
||||||
if ((idx = m_tabpanel->FindPage(m_printer_view)) != wxNOT_FOUND && idx != tpMonitor) {
|
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_printer_view->Show(false);
|
||||||
m_tabpanel->RemovePage(idx);
|
m_tabpanel->RemovePage(idx);
|
||||||
}
|
}
|
||||||
@@ -1394,8 +1390,8 @@ void MainFrame::show_device(bool should_use_native) {
|
|||||||
m_tabpanel->RemovePage(idx);
|
m_tabpanel->RemovePage(idx);
|
||||||
}
|
}
|
||||||
m_monitor->Show(false);
|
m_monitor->Show(false);
|
||||||
m_tabpanel->InsertPage(tpMonitor, m_monitor, _L("Device"), std::string("tab_monitor_active"),
|
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_monitor,
|
||||||
std::string("tab_monitor_active"));
|
_L("Device"), "tab_monitor_active");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_printer_view == nullptr) {
|
if (m_printer_view == nullptr) {
|
||||||
@@ -1416,28 +1412,31 @@ void MainFrame::show_device(bool should_use_native) {
|
|||||||
// TODO: change the bitmap
|
// TODO: change the bitmap
|
||||||
if (m_tabpanel->FindPage(m_multi_machine) == wxNOT_FOUND) {
|
if (m_tabpanel->FindPage(m_multi_machine) == wxNOT_FOUND) {
|
||||||
m_multi_machine->Show(false);
|
m_multi_machine->Show(false);
|
||||||
m_tabpanel->InsertPage(tpMultiDevice, m_multi_machine, _L("Multi-device"), std::string("tab_multi_active"),
|
// Past the web Device tab when it is already there, so enabling multi-machine
|
||||||
std::string("tab_multi_active"), false);
|
// 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) {
|
if (!m_calibration) {
|
||||||
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||||
m_calibration->SetBackgroundColour(*wxWHITE);
|
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) {
|
if (m_tabpanel->FindPage(m_calibration) == wxNOT_FOUND) {
|
||||||
m_calibration->Show(false);
|
m_calibration->Show(false);
|
||||||
m_tabpanel->AddPage(m_calibration, _L("Calibration"), std::string("tab_calibration_active"),
|
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
|
||||||
std::string("tab_calibration_active"), false);
|
_L("Calibration"), "tab_calibration_active");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((idx = m_tabpanel->FindPage(m_printer_view)) == wxNOT_FOUND) {
|
if (want_web_device_tab) {
|
||||||
m_printer_view->Show(false);
|
if ((idx = m_tabpanel->FindPage(m_printer_view)) == wxNOT_FOUND) {
|
||||||
m_tabpanel->AddPage(m_printer_view, _L("Device (Web)"), std::string("tab_monitor_active"),
|
m_printer_view->Show(false);
|
||||||
std::string("tab_monitor_active"), false);
|
// Immediately right of the native Device tab, not at the end of the tab bar.
|
||||||
} else {
|
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MONITOR_WEB,
|
||||||
m_tabpanel->SetPageText(idx, _L("Device (Web)"));
|
m_printer_view, _L("Device (Web)"), "tab_monitor_active");
|
||||||
|
} else {
|
||||||
|
m_tabpanel->SetPageText(idx, _L("Device (Web)"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _MSW_DARK_MODE
|
#ifdef _MSW_DARK_MODE
|
||||||
@@ -1445,6 +1444,7 @@ void MainFrame::show_device(bool should_use_native) {
|
|||||||
#endif // _MSW_DARK_MODE
|
#endif // _MSW_DARK_MODE
|
||||||
|
|
||||||
fit_tab_labels(); // ORCA on printer change
|
fit_tab_labels(); // ORCA on printer change
|
||||||
|
m_plugin_pages.relayout(); // re-sync plugin tabs against the native tabs just mutated above
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1466,7 +1466,8 @@ void MainFrame::show_device(bool should_use_native) {
|
|||||||
m_monitor->SetBackgroundColour(*wxWHITE);
|
m_monitor->SetBackgroundColour(*wxWHITE);
|
||||||
}
|
}
|
||||||
m_monitor->Show(false);
|
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 (wxGetApp().is_enable_multi_machine()) {
|
||||||
if (!m_multi_machine) {
|
if (!m_multi_machine) {
|
||||||
@@ -1475,18 +1476,18 @@ void MainFrame::show_device(bool should_use_native) {
|
|||||||
}
|
}
|
||||||
// TODO: change the bitmap
|
// TODO: change the bitmap
|
||||||
m_multi_machine->Show(false);
|
m_multi_machine->Show(false);
|
||||||
m_tabpanel->InsertPage(tpMultiDevice, m_multi_machine, _L("Multi-device"), std::string("tab_multi_active"),
|
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_MONITOR}), TAB_ID_MULTI_DEVICE, m_multi_machine,
|
||||||
std::string("tab_multi_active"), false);
|
_L("Multi-device"), "tab_multi_active");
|
||||||
}
|
}
|
||||||
if (!m_calibration) {
|
if (!m_calibration) {
|
||||||
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
m_calibration = new CalibrationPanel(m_tabpanel, wxID_ANY, wxDefaultPosition, wxDefaultSize);
|
||||||
m_calibration->SetBackgroundColour(*wxWHITE);
|
m_calibration->SetBackgroundColour(*wxWHITE);
|
||||||
}
|
}
|
||||||
m_calibration->Show(false);
|
m_calibration->Show(false);
|
||||||
// Calibration is always the last page, so don't use InsertPage here. Otherwise, if multi_machine page is not enabled,
|
// Last of the built-in tabs, but plugin tabs already sit past it — anchor rather than
|
||||||
// the calibration tab won't be properly added as well, due to the TabPosition::tpCalibration no longer matches the real tab position.
|
// append, so its position doesn't depend on the relayout() below running afterwards.
|
||||||
m_tabpanel->AddPage(m_calibration, _L("Calibration"), std::string("tab_calibration_active"),
|
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PROJECT}), TAB_ID_CALIBRATION, m_calibration,
|
||||||
std::string("tab_calibration_active"), false);
|
_L("Calibration"), "tab_calibration_active");
|
||||||
|
|
||||||
#ifdef _MSW_DARK_MODE
|
#ifdef _MSW_DARK_MODE
|
||||||
wxGetApp().UpdateDarkUIWin(this);
|
wxGetApp().UpdateDarkUIWin(this);
|
||||||
@@ -1519,10 +1520,17 @@ void MainFrame::show_device(bool should_use_native) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
m_printer_view->Show(false);
|
m_printer_view->Show(false);
|
||||||
m_tabpanel->InsertPage(tpMonitor, m_printer_view, _L("Device"), std::string("tab_monitor_active"),
|
m_tabpanel->InsertPage(m_tabpanel->PositionAfter({TAB_ID_PREVIEW}), TAB_ID_MONITOR, m_printer_view,
|
||||||
std::string("tab_monitor_active"));
|
_L("Device"), "tab_monitor_active");
|
||||||
}
|
}
|
||||||
fit_tab_labels(); // ORCA on printer change
|
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()
|
void MainFrame::fit_tab_labels()
|
||||||
@@ -1554,7 +1562,7 @@ void MainFrame::fit_tab_labels()
|
|||||||
bool MainFrame::preview_only_hint()
|
bool MainFrame::preview_only_hint()
|
||||||
{
|
{
|
||||||
if (m_plater && (m_plater->only_gcode_mode() || (m_plater->using_exported_file()))) {
|
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"));
|
ConfirmBeforeSendDialog confirm_dlg(this, wxID_ANY, _L("Warning"));
|
||||||
confirm_dlg.Bind(EVT_SECONDARY_CHECK_CONFIRM, [this](wxCommandEvent& e) {
|
confirm_dlg.Bind(EVT_SECONDARY_CHECK_CONFIRM, [this](wxCommandEvent& e) {
|
||||||
@@ -1872,22 +1880,22 @@ bool MainFrame::can_clone() const {
|
|||||||
|
|
||||||
bool MainFrame::can_select() 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
|
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
|
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
|
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
|
bool MainFrame::can_reslice() const
|
||||||
@@ -1996,7 +2004,7 @@ wxBoxSizer* MainFrame::create_side_tools()
|
|||||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_ALL));
|
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_ALL));
|
||||||
else
|
else
|
||||||
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE));
|
wxPostEvent(m_plater, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE));
|
||||||
this->m_tabpanel->SetSelection(tpPreview);
|
this->m_tabpanel->SelectPageByName(TAB_ID_PREVIEW);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -3143,7 +3151,7 @@ void MainFrame::init_menubar_as_editor()
|
|||||||
wxGetApp().app_config->set_bool("auto_perspective", !wxGetApp().app_config->get_bool("auto_perspective"));
|
wxGetApp().app_config->set_bool("auto_perspective", !wxGetApp().app_config->get_bool("auto_perspective"));
|
||||||
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
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);
|
[this]() { return wxGetApp().app_config->get_bool("auto_perspective"); }, this);
|
||||||
|
|
||||||
viewMenu->AppendSeparator();
|
viewMenu->AppendSeparator();
|
||||||
@@ -3152,7 +3160,7 @@ void MainFrame::init_menubar_as_editor()
|
|||||||
wxGetApp().toggle_show_gcode_window();
|
wxGetApp().toggle_show_gcode_window();
|
||||||
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
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);
|
[this]() { return wxGetApp().show_gcode_window(); }, this);
|
||||||
|
|
||||||
append_menu_check_item(
|
append_menu_check_item(
|
||||||
@@ -3161,7 +3169,7 @@ void MainFrame::init_menubar_as_editor()
|
|||||||
wxGetApp().toggle_show_3d_navigator();
|
wxGetApp().toggle_show_3d_navigator();
|
||||||
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
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);
|
[this]() { return wxGetApp().show_3d_navigator(); }, this);
|
||||||
|
|
||||||
append_menu_check_item(viewMenu, wxID_ANY, _L("Show Gridlines"), _L("Show Gridlines on plate"),
|
append_menu_check_item(viewMenu, wxID_ANY, _L("Show Gridlines"), _L("Show Gridlines on plate"),
|
||||||
@@ -3169,15 +3177,14 @@ void MainFrame::init_menubar_as_editor()
|
|||||||
wxGetApp().toggle_show_plate_gridlines();
|
wxGetApp().toggle_show_plate_gridlines();
|
||||||
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
||||||
}, this,
|
}, 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);
|
[this]() { return wxGetApp().show_plate_gridlines(); }, this);
|
||||||
|
|
||||||
append_menu_item(
|
append_menu_item(
|
||||||
viewMenu, wxID_ANY, _L("Reset Window Layout"), _L("Reset to default window layout"),
|
viewMenu, wxID_ANY, _L("Reset Window Layout"), _L("Reset to default window layout"),
|
||||||
[this](wxCommandEvent&) { m_plater->reset_window_layout(); }, "", this,
|
[this](wxCommandEvent&) { m_plater->reset_window_layout(); }, "", this,
|
||||||
[this]() {
|
[this]() {
|
||||||
return (m_tabpanel->GetSelection() == TabPosition::tp3DEditor || m_tabpanel->GetSelection() == TabPosition::tpPreview) &&
|
return is_prepare_or_preview_tab() && m_plater->is_sidebar_enabled();
|
||||||
m_plater->is_sidebar_enabled();
|
|
||||||
},
|
},
|
||||||
this);
|
this);
|
||||||
|
|
||||||
@@ -3199,7 +3206,7 @@ void MainFrame::init_menubar_as_editor()
|
|||||||
wxGetApp().toggle_show_outline();
|
wxGetApp().toggle_show_outline();
|
||||||
m_plater->get_current_canvas3D()->post_event(SimpleEvent(wxEVT_PAINT));
|
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);
|
[this]() { return wxGetApp().show_outline(); }, this);
|
||||||
|
|
||||||
/*viewMenu->AppendSeparator();
|
/*viewMenu->AppendSeparator();
|
||||||
@@ -4000,13 +4007,16 @@ void MainFrame::select_tab(wxPanel* panel)
|
|||||||
wxGetApp().params_dialog()->Popup();
|
wxGetApp().params_dialog()->Popup();
|
||||||
return;
|
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);
|
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<size_t>(page_idx));
|
||||||
|
if (page_name == TAB_ID_PREPARE && m_tabpanel->GetSelectedPageName() == TAB_ID_PREVIEW)
|
||||||
return;
|
return;
|
||||||
//BBS GUI refactor: remove unused layout new/dlg
|
//BBS GUI refactor: remove unused layout new/dlg
|
||||||
/*if (page_idx != wxNOT_FOUND && m_layout == ESettingsLayout::Dlg)
|
/*if (page_idx != wxNOT_FOUND && m_layout == ESettingsLayout::Dlg)
|
||||||
page_idx++;*/
|
page_idx++;*/
|
||||||
select_tab(size_t(page_idx));
|
select_tab(page_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
//BBS
|
//BBS
|
||||||
@@ -4014,7 +4024,7 @@ void MainFrame::jump_to_monitor(std::string dev_id)
|
|||||||
{
|
{
|
||||||
if(!m_monitor)
|
if(!m_monitor)
|
||||||
return;
|
return;
|
||||||
m_tabpanel->SetSelection(tpMonitor);
|
m_tabpanel->SelectPageByName(TAB_ID_MONITOR);
|
||||||
if (!dev_id.empty()) {
|
if (!dev_id.empty()) {
|
||||||
((MonitorPanel*)m_monitor)->select_machine(dev_id);
|
((MonitorPanel*)m_monitor)->select_machine(dev_id);
|
||||||
}
|
}
|
||||||
@@ -4024,26 +4034,26 @@ void MainFrame::jump_to_multipage()
|
|||||||
{
|
{
|
||||||
if(!m_multi_machine)
|
if(!m_multi_machine)
|
||||||
return;
|
return;
|
||||||
m_tabpanel->SetSelection(tpMultiDevice);
|
m_tabpanel->SelectPageByName(TAB_ID_MULTI_DEVICE);
|
||||||
((MultiMachinePage*)m_multi_machine)->jump_to_send_page();
|
((MultiMachinePage*)m_multi_machine)->jump_to_send_page();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//BBS GUI refactor: remove unused layout new/dlg
|
//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;
|
//bool tabpanel_was_hidden = false;
|
||||||
|
|
||||||
// Controls on page are created on active page of active tab now.
|
// 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
|
// We should select/activate tab before its showing to avoid an UI-flickering
|
||||||
auto select = [this, tab](bool was_hidden) {
|
auto select = [this, id](bool was_hidden) {
|
||||||
// when tab == -1, it means we should show the last selected tab
|
// when id is empty, it means we should show the last selected tab
|
||||||
//BBS GUI refactor: remove unused layout new/dlg
|
//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 : (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)
|
if (m_tabpanel->GetSelectedPageName() != new_selection)
|
||||||
m_tabpanel->SetSelection(new_selection);
|
m_tabpanel->SelectPageByName(new_selection);
|
||||||
#ifdef _MSW_DARK_MODE
|
#ifdef _MSW_DARK_MODE
|
||||||
/*if (wxGetApp().tabs_as_menu()) {
|
/*if (wxGetApp().tabs_as_menu()) {
|
||||||
if (Tab* cur_tab = dynamic_cast<Tab*>(m_tabpanel->GetPage(new_selection)))
|
if (Tab* cur_tab = dynamic_cast<Tab*>(m_tabpanel->GetPage(new_selection)))
|
||||||
@@ -4052,10 +4062,12 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
|
|||||||
m_plater->get_current_canvas3D()->render();
|
m_plater->get_current_canvas3D()->render();
|
||||||
}*/
|
}*/
|
||||||
#endif
|
#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();
|
m_plater->canvas3D()->render();
|
||||||
else if (was_hidden) {
|
else if (was_hidden) {
|
||||||
Tab* cur_tab = dynamic_cast<Tab*>(m_tabpanel->GetPage(new_selection));
|
Tab* cur_tab = dynamic_cast<Tab*>(m_tabpanel->GetPageByName(new_selection));
|
||||||
if (cur_tab)
|
if (cur_tab)
|
||||||
cur_tab->OnActivate();
|
cur_tab->OnActivate();
|
||||||
}
|
}
|
||||||
@@ -4064,10 +4076,10 @@ void MainFrame::select_tab(size_t tab/* = size_t(-1)*/)
|
|||||||
select(false);
|
select(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainFrame::request_select_tab(TabPosition pos)
|
void MainFrame::request_select_tab(const wxString& id)
|
||||||
{
|
{
|
||||||
wxCommandEvent* evt = new wxCommandEvent(EVT_SELECT_TAB);
|
wxCommandEvent* evt = new wxCommandEvent(EVT_SELECT_TAB);
|
||||||
evt->SetInt(pos);
|
evt->SetString(id);
|
||||||
wxQueueEvent(this, evt);
|
wxQueueEvent(this, evt);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -4359,7 +4371,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()
|
void MainFrame::refresh_plugin_tips()
|
||||||
|
|||||||
@@ -35,6 +35,21 @@
|
|||||||
#include "PrinterWebView.hpp"
|
#include "PrinterWebView.hpp"
|
||||||
#include "calib_dlg.hpp"
|
#include "calib_dlg.hpp"
|
||||||
#include "MultiMachinePage.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"
|
||||||
|
#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
|
#define ENABEL_PRINT_ALL 0
|
||||||
|
|
||||||
@@ -115,7 +130,7 @@ class MainFrame : public DPIFrame
|
|||||||
wxMenuItem* m_menu_item_reslice_now { nullptr };
|
wxMenuItem* m_menu_item_reslice_now { nullptr };
|
||||||
wxSizer* m_main_sizer{ 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_base_name(const wxString &full_name, const char *extension = nullptr) const;
|
||||||
std::string get_dir_name(const wxString &full_name) const;
|
std::string get_dir_name(const wxString &full_name) const;
|
||||||
@@ -214,19 +229,6 @@ public:
|
|||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
bool get_mac_full_screen() { return m_mac_fullscreen; }
|
bool get_mac_full_screen() { return m_mac_fullscreen; }
|
||||||
#endif
|
#endif
|
||||||
//BBS GUI refactor
|
|
||||||
enum TabPosition
|
|
||||||
{
|
|
||||||
tpHome = 0,
|
|
||||||
tp3DEditor = 1,
|
|
||||||
tpPreview = 2,
|
|
||||||
tpMonitor = 3,
|
|
||||||
tpMultiDevice = 4,
|
|
||||||
tpProject = 5,
|
|
||||||
tpCalibration = 6,
|
|
||||||
tpAuxiliary = 7,
|
|
||||||
toDebugTool = 8,
|
|
||||||
};
|
|
||||||
|
|
||||||
//BBS: add slice&&print status update logic
|
//BBS: add slice&&print status update logic
|
||||||
enum SlicePrintEventType
|
enum SlicePrintEventType
|
||||||
@@ -326,8 +328,8 @@ public:
|
|||||||
// When tab == -1, will be selected last selected tab
|
// When tab == -1, will be selected last selected tab
|
||||||
//BBS: GUI refactor
|
//BBS: GUI refactor
|
||||||
void select_tab(wxPanel* panel);
|
void select_tab(wxPanel* panel);
|
||||||
void select_tab(size_t tab = size_t(-1));
|
void select_tab(const wxString& id = wxString());
|
||||||
void request_select_tab(TabPosition pos);
|
void request_select_tab(const wxString& id);
|
||||||
int get_calibration_curr_tab();
|
int get_calibration_curr_tab();
|
||||||
void select_view(const std::string& direction);
|
void select_view(const std::string& direction);
|
||||||
// Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig
|
// Propagate changed configuration from the Tab to the Plater and save changes to the AppConfig
|
||||||
@@ -360,6 +362,9 @@ public:
|
|||||||
//SoftFever
|
//SoftFever
|
||||||
void show_device(bool should_use_native);
|
void show_device(bool should_use_native);
|
||||||
void fit_tab_labels(); // ORCA
|
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 };
|
PA_Calibration_Dlg* m_pa_calib_dlg{ nullptr };
|
||||||
FlowRateCalibrationDialog* m_flow_rate_calib_dlg{ nullptr };
|
FlowRateCalibrationDialog* m_flow_rate_calib_dlg{ nullptr };
|
||||||
@@ -385,7 +390,8 @@ public:
|
|||||||
CalibrationPanel* m_calibration{ nullptr };
|
CalibrationPanel* m_calibration{ nullptr };
|
||||||
WebViewPanel* m_webview { nullptr };
|
WebViewPanel* m_webview { nullptr };
|
||||||
PrinterWebView* m_printer_view{nullptr};
|
PrinterWebView* m_printer_view{nullptr};
|
||||||
wxLogWindow* m_log_window { nullptr };
|
PluginPages m_plugin_pages;
|
||||||
|
wxLogWindow* m_log_window { nullptr };
|
||||||
// BBS
|
// BBS
|
||||||
//wxBookCtrlBase* m_tabpanel { nullptr };
|
//wxBookCtrlBase* m_tabpanel { nullptr };
|
||||||
Notebook* m_tabpanel{ nullptr };
|
Notebook* m_tabpanel{ nullptr };
|
||||||
|
|||||||
@@ -186,17 +186,17 @@ void MonitorPanel::init_tabpanel()
|
|||||||
|
|
||||||
//m_status_add_machine_panel = new AddMachinePanel(m_tabpanel);
|
//m_status_add_machine_panel = new AddMachinePanel(m_tabpanel);
|
||||||
m_status_info_panel = new StatusPanel(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_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("Storage"), false);
|
||||||
//m_tabpanel->AddPage(m_media_file_panel, _L("Internal Storage"), "", false);
|
//m_tabpanel->AddPage(m_media_file_panel, _L("Internal Storage"), false);
|
||||||
|
|
||||||
m_upgrade_panel = new UpgradePanel(m_tabpanel);
|
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_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();
|
std::string network_ver = Slic3r::NetworkAgent::get_version();
|
||||||
if (!network_ver.empty()) {
|
if (!network_ver.empty()) {
|
||||||
@@ -413,7 +413,10 @@ void MonitorPanel::update_hms_tag()
|
|||||||
bool MonitorPanel::Show(bool show)
|
bool MonitorPanel::Show(bool show)
|
||||||
{
|
{
|
||||||
#ifdef __APPLE__
|
#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
|
#endif
|
||||||
|
|
||||||
NetworkAgent* m_agent = wxGetApp().getAgent();
|
NetworkAgent* m_agent = wxGetApp().getAgent();
|
||||||
|
|||||||
@@ -86,9 +86,9 @@ void MultiMachinePage::init_tabpanel()
|
|||||||
m_cloud_task_manager = new CloudTaskManagerPage(m_tabpanel);
|
m_cloud_task_manager = new CloudTaskManagerPage(m_tabpanel);
|
||||||
m_machine_manager = new MultiMachineManagerPage(m_tabpanel);
|
m_machine_manager = new MultiMachineManagerPage(m_tabpanel);
|
||||||
|
|
||||||
m_tabpanel->AddPage(m_machine_manager, _L("Device"), "", true);
|
m_tabpanel->AddPage(m_machine_manager, _L("Device"), true);
|
||||||
m_tabpanel->AddPage(m_local_task_manager, _L("Task Sending"), "", false);
|
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_cloud_task_manager, _L("Task Sent"), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MultiMachinePage::init_timer()
|
void MultiMachinePage::init_timer()
|
||||||
|
|||||||
@@ -120,11 +120,11 @@ void ButtonsListCtrl::Rescale()
|
|||||||
|
|
||||||
void ButtonsListCtrl::SetSelection(int sel)
|
void ButtonsListCtrl::SetSelection(int sel)
|
||||||
{
|
{
|
||||||
if (m_selection == sel)
|
if (m_selection == sel && sel >= 0 && sel < static_cast<int>(m_pageButtons.size()))
|
||||||
return;
|
return;
|
||||||
// BBS: change button color
|
// BBS: change button color
|
||||||
wxColour selected_btn_bg("#009688"); // Gradient #009688
|
wxColour selected_btn_bg("#009688"); // Gradient #009688
|
||||||
if (m_selection >= 0) {
|
if (m_selection >= 0 && m_selection < static_cast<int>(m_pageButtons.size())) {
|
||||||
StateColor bg_color = StateColor(
|
StateColor bg_color = StateColor(
|
||||||
std::pair{wxColour(107, 107, 107), (int) StateColor::Hovered},
|
std::pair{wxColour(107, 107, 107), (int) StateColor::Hovered},
|
||||||
std::pair{wxColour(59, 68, 70), (int) StateColor::Normal});
|
std::pair{wxColour(59, 68, 70), (int) StateColor::Normal});
|
||||||
@@ -132,9 +132,15 @@ void ButtonsListCtrl::SetSelection(int sel)
|
|||||||
StateColor text_color = StateColor(
|
StateColor text_color = StateColor(
|
||||||
std::pair{wxColour(254,254, 254), (int) StateColor::Normal}
|
std::pair{wxColour(254,254, 254), (int) StateColor::Normal}
|
||||||
);
|
);
|
||||||
m_pageButtons[m_selection]->SetSelected(false);
|
|
||||||
m_pageButtons[m_selection]->SetTextColor(text_color);
|
m_pageButtons[m_selection]->SetTextColor(text_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sel < 0 || sel >= static_cast<int>(m_pageButtons.size())) {
|
||||||
|
m_selection = -1;
|
||||||
|
Refresh();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_selection = sel;
|
m_selection = sel;
|
||||||
|
|
||||||
StateColor bg_color = StateColor(
|
StateColor bg_color = StateColor(
|
||||||
@@ -145,17 +151,19 @@ void ButtonsListCtrl::SetSelection(int sel)
|
|||||||
StateColor text_color = StateColor(
|
StateColor text_color = StateColor(
|
||||||
std::pair{wxColour(254, 254, 254), (int) StateColor::Normal}
|
std::pair{wxColour(254, 254, 254), (int) StateColor::Normal}
|
||||||
);
|
);
|
||||||
m_pageButtons[m_selection]->SetSelected(true);
|
|
||||||
m_pageButtons[m_selection]->SetTextColor(text_color);
|
m_pageButtons[m_selection]->SetTextColor(text_color);
|
||||||
|
|
||||||
Refresh();
|
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);
|
Button * btn = new Button(this, text.empty() ? text : " " + text, bmp_name, wxNO_BORDER);
|
||||||
btn->SetCornerRadius(0);
|
btn->SetCornerRadius(0);
|
||||||
|
|
||||||
|
if (bmp_name.empty() && bmp.IsOk())
|
||||||
|
btn->SetIcon(bmp);
|
||||||
|
|
||||||
int em = em_unit(this);
|
int em = em_unit(this);
|
||||||
//BBS set size for button
|
//BBS set size for button
|
||||||
btn->SetMinSize({(text.empty() ? 40 : 136) * em / 10, 36 * em / 10});
|
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(
|
StateColor text_color = StateColor(
|
||||||
std::pair{wxColour(254,254, 254), (int) StateColor::Normal});
|
std::pair{wxColour(254,254, 254), (int) StateColor::Normal});
|
||||||
btn->SetTextColor(text_color);
|
btn->SetTextColor(text_color);
|
||||||
btn->SetInactiveIcon(inactive_bmp_name);
|
|
||||||
btn->SetSelected(false);
|
|
||||||
btn->Bind(wxEVT_BUTTON, [this, btn](wxCommandEvent& event) {
|
btn->Bind(wxEVT_BUTTON, [this, btn](wxCommandEvent& event) {
|
||||||
if (auto it = std::find(m_pageButtons.begin(), m_pageButtons.end(), btn); it != m_pageButtons.end()) {
|
if (auto it = std::find(m_pageButtons.begin(), m_pageButtons.end(), btn); it != m_pageButtons.end()) {
|
||||||
auto sel = it - m_pageButtons.begin();
|
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)
|
void ButtonsListCtrl::RemovePage(size_t n)
|
||||||
{
|
{
|
||||||
|
if (n >= m_pageButtons.size())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_selection == static_cast<int>(n))
|
||||||
|
m_selection = -1;
|
||||||
|
else if (m_selection > static_cast<int>(n))
|
||||||
|
--m_selection;
|
||||||
|
|
||||||
Button* btn = m_pageButtons[n];
|
Button* btn = m_pageButtons[n];
|
||||||
m_pageButtons.erase(m_pageButtons.begin() + n);
|
m_pageButtons.erase(m_pageButtons.begin() + n);
|
||||||
m_pageLabels.erase(m_pageLabels.begin() + n); // ORCA
|
m_pageLabels.erase(m_pageLabels.begin() + n); // ORCA
|
||||||
@@ -240,6 +254,24 @@ wxString ButtonsListCtrl::GetPageText(size_t n) const
|
|||||||
return btn->GetLabel();
|
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
|
//#endif // _WIN32
|
||||||
|
|
||||||
void Notebook::Init()
|
void Notebook::Init()
|
||||||
@@ -253,6 +285,8 @@ void Notebook::Init()
|
|||||||
|
|
||||||
m_showTimeout = m_hideTimeout = 0;
|
m_showTimeout = m_hideTimeout = 0;
|
||||||
|
|
||||||
|
m_pageNames.clear();
|
||||||
|
|
||||||
/* On Linux, Gstreamer wxMediaCtrl does not seem to get along well with
|
/* 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
|
* 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 ...
|
* bug? Is this a Gstreamer bug? No idea, but it is our problem ...
|
||||||
|
|||||||
+106
-33
@@ -3,7 +3,11 @@
|
|||||||
|
|
||||||
//#ifdef _WIN32
|
//#ifdef _WIN32
|
||||||
|
|
||||||
|
#include <initializer_list>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
#include <wx/bookctrl.h>
|
#include <wx/bookctrl.h>
|
||||||
|
#include <wx/bitmap.h>
|
||||||
#include <wx/sizer.h>
|
#include <wx/sizer.h>
|
||||||
|
|
||||||
class ScalableButton;
|
class ScalableButton;
|
||||||
@@ -23,13 +27,16 @@ public:
|
|||||||
void SetSelection(int sel);
|
void SetSelection(int sel);
|
||||||
void UpdateMode();
|
void UpdateMode();
|
||||||
void Rescale();
|
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);
|
void RemovePage(size_t n);
|
||||||
bool SetPageImage(size_t n, const std::string& bmp_name) const;
|
bool SetPageImage(size_t n, const std::string& bmp_name) const;
|
||||||
void SetPageText(size_t n, const wxString& strText);
|
void SetPageText(size_t n, const wxString& strText);
|
||||||
void SetCompact(size_t n, bool compact); // ORCA
|
void SetCompact(size_t n, bool compact); // ORCA
|
||||||
wxString GetPageText(size_t n) const;
|
wxString GetPageText(size_t n) const;
|
||||||
wxFlexGridSizer* GetBtnsSizer(){return m_buttons_sizer;}; // ORCA
|
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:
|
private:
|
||||||
wxFlexGridSizer* m_buttons_sizer;
|
wxFlexGridSizer* m_buttons_sizer;
|
||||||
@@ -40,9 +47,10 @@ private:
|
|||||||
int m_btn_margin;
|
int m_btn_margin;
|
||||||
int m_line_margin;
|
int m_line_margin;
|
||||||
std::vector<wxString> m_pageLabels; // ORCA
|
std::vector<wxString> m_pageLabels; // ORCA
|
||||||
|
wxWindow* m_overflow_button{nullptr}; // ORCA
|
||||||
};
|
};
|
||||||
|
|
||||||
class Notebook: public wxBookCtrlBase
|
class Notebook : public wxBookCtrlBase
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
Notebook(wxWindow * parent,
|
Notebook(wxWindow * parent,
|
||||||
@@ -103,7 +111,7 @@ public:
|
|||||||
// by this control) and show it immediately.
|
// by this control) and show it immediately.
|
||||||
bool ShowNewPage(wxWindow * page)
|
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.
|
// Implement base class pure virtual methods.
|
||||||
|
|
||||||
// adds a new page to the control
|
// Page management. Every insertion funnels through the InsertPage() below; `id` is the
|
||||||
bool AddPage(wxWindow* page,
|
// 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 wxString& text,
|
||||||
const std::string& bmp_name,
|
const std::string& bmp_name = "",
|
||||||
const std::string& inactive_bmp_name,
|
|
||||||
bool bSelect = false)
|
bool bSelect = false)
|
||||||
{
|
{
|
||||||
DoInvalidateBestSize();
|
DoInvalidateBestSize();
|
||||||
return InsertPage(GetPageCount(), page, text, bmp_name, inactive_bmp_name, bSelect);
|
return InsertPage(GetPageCount(), id, page, text, bmp_name, bSelect);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Page management
|
bool AddPage(wxWindow* page, const wxString& text, bool bSelect = false, int imageId = NO_IMAGE) override
|
||||||
virtual bool InsertPage(size_t n,
|
|
||||||
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;
|
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))
|
if (!DoSetSelectionAfterInsertion(n, bSelect))
|
||||||
page->Hide();
|
page->Hide();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InsertPage(size_t n,
|
virtual bool InsertPage(size_t n,
|
||||||
wxWindow * page,
|
wxWindow * page,
|
||||||
const wxString & text,
|
const wxString & text,
|
||||||
const std::string& bmp_name = "",
|
bool bSelect = false,
|
||||||
const std::string& inactive_bmp_name = "",
|
int WXUNUSED(imageId) = NO_IMAGE) override
|
||||||
bool bSelect = false)
|
|
||||||
{
|
{
|
||||||
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect))
|
return InsertPage(n, wxString(), page, text, "", bSelect);
|
||||||
return false;
|
|
||||||
|
|
||||||
GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name, inactive_bmp_name);
|
|
||||||
|
|
||||||
if (bSelect)
|
|
||||||
SetSelection(n);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual int SetSelection(size_t n) override
|
virtual int SetSelection(size_t n) override
|
||||||
@@ -211,8 +224,8 @@ public:
|
|||||||
return DoSetSelection(n);
|
return DoSetSelection(n);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Neither labels nor images are supported but we still store the labels
|
// Labels are stored by the custom button list; wx's image-list API is unused — tab icons
|
||||||
// just in case the user code attaches some importance to them.
|
// 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
|
virtual bool SetPageText(size_t n, const wxString & strText) override
|
||||||
{
|
{
|
||||||
wxCHECK_MSG(n < GetPageCount(), false, wxS("Invalid page"));
|
wxCHECK_MSG(n < GetPageCount(), false, wxS("Invalid page"));
|
||||||
@@ -251,7 +264,64 @@ public:
|
|||||||
page->SetFocus();
|
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<ButtonsListCtrl*>(m_bookctrl); }
|
ButtonsListCtrl* GetBtnsListCtrl() const { return static_cast<ButtonsListCtrl*>(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<const char*> ids) const
|
||||||
|
{
|
||||||
|
for (const char* id : ids)
|
||||||
|
if (const int idx = FindPageByName(id); idx != wxNOT_FOUND)
|
||||||
|
return static_cast<size_t>(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<int>(i);
|
||||||
|
return wxNOT_FOUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
wxWindow* GetPageByName(const wxString& id) const
|
||||||
|
{
|
||||||
|
const int idx = FindPageByName(id);
|
||||||
|
return idx == wxNOT_FOUND ? nullptr : GetPage(static_cast<size_t>(idx));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SelectPageByName(const wxString& id)
|
||||||
|
{
|
||||||
|
const int idx = FindPageByName(id);
|
||||||
|
if (idx == wxNOT_FOUND)
|
||||||
|
return false;
|
||||||
|
SetSelection(static_cast<size_t>(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<size_t>(sel));
|
||||||
|
}
|
||||||
|
|
||||||
void UpdateMode()
|
void UpdateMode()
|
||||||
{
|
{
|
||||||
@@ -369,6 +439,7 @@ protected:
|
|||||||
wxWindow* const win = wxBookCtrlBase::DoRemovePage(page);
|
wxWindow* const win = wxBookCtrlBase::DoRemovePage(page);
|
||||||
if (win)
|
if (win)
|
||||||
{
|
{
|
||||||
|
m_pageNames.erase(m_pageNames.begin() + page);
|
||||||
GetBtnsListCtrl()->RemovePage(page);
|
GetBtnsListCtrl()->RemovePage(page);
|
||||||
DoSetSelectionAfterRemoval(page);
|
DoSetSelectionAfterRemoval(page);
|
||||||
}
|
}
|
||||||
@@ -394,6 +465,8 @@ protected:
|
|||||||
private:
|
private:
|
||||||
void Init();
|
void Init();
|
||||||
|
|
||||||
|
std::vector<wxString> m_pageNames; // index-parallel to wxBookCtrlBase::m_pages
|
||||||
|
|
||||||
wxShowEffect m_showEffect,
|
wxShowEffect m_showEffect,
|
||||||
m_hideEffect;
|
m_hideEffect;
|
||||||
|
|
||||||
|
|||||||
@@ -1918,7 +1918,7 @@ void NotificationManager::push_validate_error_notification(StringObjectException
|
|||||||
wxGetApp().sidebar().jump_to_option(opt, Preset::TYPE_PRINT, L"");
|
wxGetApp().sidebar().jump_to_option(opt, Preset::TYPE_PRINT, L"");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
@@ -1985,7 +1985,7 @@ void NotificationManager::push_validate_error_notification(StringObjectException
|
|||||||
wxGetApp().sidebar().jump_to_option(opt, opt_type, L"");
|
wxGetApp().sidebar().jump_to_option(opt, opt_type, L"");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||||
}
|
}
|
||||||
return false;
|
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 (iter != objects.end()) { ovs.push_back({ *iter, nullptr }); }
|
||||||
}
|
}
|
||||||
if (!ovs.empty()) {
|
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()->select_items(ovs);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -2046,7 +2046,7 @@ void NotificationManager::push_slicing_warning_notification(const std::string& t
|
|||||||
auto& objects = wxGetApp().model().objects;
|
auto& objects = wxGetApp().model().objects;
|
||||||
auto iter = std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; });
|
auto iter = std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; });
|
||||||
if (iter != 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()->select_items({ {*iter, nullptr} });
|
||||||
}
|
}
|
||||||
return false;
|
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 (iter != objects.end()) { ovs.push_back({ *iter, nullptr }); }
|
||||||
}
|
}
|
||||||
if (!ovs.empty()) {
|
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()->select_items(ovs);
|
||||||
wxGetApp().obj_list()->update_selections_on_canvas();
|
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()) {
|
if (!sel_items.empty()) {
|
||||||
obj_list->select_items(sel_items);
|
obj_list->select_items(sel_items);
|
||||||
|
|||||||
+43
-32
@@ -5783,6 +5783,8 @@ private:
|
|||||||
bool show_warning_dialog { false };
|
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_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_3mf(".*3mf", std::regex::icase);
|
||||||
const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", 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
|
#endif // WIN32
|
||||||
|
|
||||||
m_mainframe.Raise();
|
m_mainframe.Raise();
|
||||||
m_mainframe.select_tab(size_t(MainFrame::tp3DEditor));
|
m_mainframe.select_tab(TAB_ID_PREPARE);
|
||||||
if (wxGetApp().is_editor())
|
if (wxGetApp().is_editor())
|
||||||
m_plater.select_view_3D("3D");
|
m_plater.select_view_3D("3D");
|
||||||
|
|
||||||
@@ -6579,9 +6581,9 @@ void Plater::priv::select_next_view_3D()
|
|||||||
{
|
{
|
||||||
|
|
||||||
if (current_panel == view3D)
|
if (current_panel == view3D)
|
||||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tpPreview));
|
wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
|
||||||
else if (current_panel == 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)
|
// else if (current_panel == assemble_view)
|
||||||
// set_current_panel(view3D);
|
// set_current_panel(view3D);
|
||||||
}
|
}
|
||||||
@@ -7880,7 +7882,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||||||
q->select_plate(first_plate_index);
|
q->select_plate(first_plate_index);
|
||||||
//set to 3d tab
|
//set to 3d tab
|
||||||
q->select_view_3D("Preview");
|
q->select_view_3D("Preview");
|
||||||
wxGetApp().mainframe->select_tab(MainFrame::tpPreview);
|
wxGetApp().mainframe->select_tab(TAB_ID_PREVIEW);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//set to 3d tab
|
//set to 3d tab
|
||||||
@@ -7899,7 +7901,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||||||
else {
|
else {
|
||||||
//always set to 3D after loading files
|
//always set to 3D after loading files
|
||||||
q->select_view_3D("3D");
|
q->select_view_3D("3D");
|
||||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (load_model) {
|
if (load_model) {
|
||||||
@@ -8807,7 +8809,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) {
|
if (inst_idx != -1) {
|
||||||
auto* model = wxGetApp().obj_list()->GetModel();
|
auto* model = wxGetApp().obj_list()->GetModel();
|
||||||
@@ -8836,7 +8838,7 @@ void Plater::priv::process_validation_warning(StringObjectException const &warni
|
|||||||
} else {
|
} else {
|
||||||
auto iter = id.id ? std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; }) : objects.end();
|
auto iter = id.id ? std::find_if(objects.begin(), objects.end(), [id](auto o) { return o->id() == id; }) : objects.end();
|
||||||
if (iter != 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()->select_items({{*iter, nullptr}});
|
||||||
wxGetApp().obj_list()->update_selections_on_canvas();
|
wxGetApp().obj_list()->update_selections_on_canvas();
|
||||||
}
|
}
|
||||||
@@ -11220,13 +11222,19 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const int new_sel = e.GetSelection();
|
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();
|
update_sidebar();
|
||||||
int old_sel = e.GetOldSelection();
|
int old_sel = e.GetOldSelection();
|
||||||
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
|
const bool use_printer_agents = wxGetApp().app_config->get_bool("use_printer_agents");
|
||||||
const bool use_native_device_tab = wxGetApp().preset_bundle &&
|
const bool use_native_device_tab = wxGetApp().preset_bundle &&
|
||||||
(wxGetApp().preset_bundle->use_bbl_device_tab() || use_printer_agents);
|
(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.
|
// BBL network module is only required for BBL-vendor printers.
|
||||||
// Non-BBL Python plugins (e.g. moonraker) drive the Device tab without it.
|
// 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()) {
|
if (!use_printer_agents && wxGetApp().preset_bundle->is_bbl_vendor() && !Slic3r::NetworkAgent::is_network_module_loaded()) {
|
||||||
@@ -11238,12 +11246,15 @@ void Plater::priv::on_tab_selection_changing(wxBookCtrlEvent& e)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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 &&
|
const bool selecting_web_device_tab = main_frame->m_printer_view &&
|
||||||
main_frame->m_tabpanel->GetPage(new_sel) == main_frame->m_printer_view;
|
main_frame->m_tabpanel->GetPage(new_sel) == main_frame->m_printer_view;
|
||||||
if (selecting_web_device_tab) {
|
if (selecting_web_device_tab) {
|
||||||
// Use the selected discovered machine when the preset has no host.
|
// Use the selected discovered machine when the preset has no host.
|
||||||
main_frame->load_printer_url();
|
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;
|
auto cfg = wxGetApp().preset_bundle->printers.get_edited_preset().config;
|
||||||
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
|
wxString url = from_u8(PrintHost::get_print_host_webui(&cfg));
|
||||||
if (main_frame->m_printer_view && url.empty()) {
|
if (main_frame->m_printer_view && url.empty()) {
|
||||||
@@ -12106,7 +12117,7 @@ bool Plater::priv::check_ams_status_impl(bool is_slice_all)
|
|||||||
wxPostEvent(q, SimpleEvent(EVT_GLTOOLBAR_SLICE_ALL));
|
wxPostEvent(q, SimpleEvent(EVT_GLTOOLBAR_SLICE_ALL));
|
||||||
else
|
else
|
||||||
wxPostEvent(q, SimpleEvent(EVT_GLTOOLBAR_SLICE_PLATE));
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -12674,7 +12685,7 @@ void Plater::priv::take_snapshot(const std::string& snapshot_name, const UndoRed
|
|||||||
ModelWipeTower& tower = model.wipe_tower;
|
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.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();
|
const GLGizmosManager& gizmos = get_current_canvas3D()->get_canvas_type() == GLCanvas3D::CanvasAssembleView ? assemble_view->get_canvas3d()->get_gizmos_manager() : view3D->get_canvas3d()->get_gizmos_manager();
|
||||||
@@ -12784,7 +12795,7 @@ void Plater::priv::undo_redo_to(std::vector<UndoRedo::Snapshot>::const_iterator
|
|||||||
ModelWipeTower& tower = model.wipe_tower;
|
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.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;
|
const int layer_range_idx = it_snapshot->snapshot_data.layer_range_idx;
|
||||||
@@ -13063,7 +13074,7 @@ int Plater::new_project(bool skip_confirm, bool silent, const wxString& project_
|
|||||||
get_notification_manager()->clear_all();
|
get_notification_manager()->clear_all();
|
||||||
|
|
||||||
if (!silent)
|
if (!silent)
|
||||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||||
|
|
||||||
//get_partplate_list().reinit();
|
//get_partplate_list().reinit();
|
||||||
//get_partplate_list().update_slice_context_to_current_plate(p->background_process);
|
//get_partplate_list().update_slice_context_to_current_plate(p->background_process);
|
||||||
@@ -13212,7 +13223,7 @@ void Plater::load_project(wxString const& filename2,
|
|||||||
if (!m_exported_file) {
|
if (!m_exported_file) {
|
||||||
p->select_view("topfront");
|
p->select_view("topfront");
|
||||||
p->camera.requires_zoom_to_plate = REQUIRES_ZOOM_TO_ALL_PLATE;
|
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 {
|
else {
|
||||||
p->partplate_list.select_plate_view();
|
p->partplate_list.select_plate_view();
|
||||||
@@ -13326,7 +13337,7 @@ void Plater::import_model_id(wxString download_info)
|
|||||||
const int max_retries = 3;
|
const int max_retries = 3;
|
||||||
|
|
||||||
/* jump to 3D eidtor */
|
/* jump to 3D eidtor */
|
||||||
wxGetApp().mainframe->select_tab((size_t)MainFrame::TabPosition::tp3DEditor);
|
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||||
|
|
||||||
/* prepare progress dialog */
|
/* prepare progress dialog */
|
||||||
bool cont = true;
|
bool cont = true;
|
||||||
@@ -13635,7 +13646,7 @@ void Plater::calib_pa(const Calib_Params& params)
|
|||||||
{
|
{
|
||||||
const auto calib_pa_name = wxString::Format(L"Pressure Advance Test");
|
const auto calib_pa_name = wxString::Format(L"Pressure Advance Test");
|
||||||
new_project(false, false, calib_pa_name);
|
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 print_config = &wxGetApp().preset_bundle->prints.get_edited_preset().config;
|
||||||
auto printer_config = &wxGetApp().preset_bundle->printers.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));
|
print_config->set_key_value("overhang_reverse", new ConfigOptionBool(false));
|
||||||
@@ -14116,7 +14127,7 @@ void Plater::calib_flowrate(bool is_linear, int pass, InfillPattern pattern) {
|
|||||||
if (new_project(false, false, calib_name) == wxID_CANCEL)
|
if (new_project(false, false, calib_name) == wxID_CANCEL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tp3DEditor));
|
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||||
|
|
||||||
if (is_linear) {
|
if (is_linear) {
|
||||||
if (pass == 1)
|
if (pass == 1)
|
||||||
@@ -14153,7 +14164,7 @@ void Plater::calib_temp(const Calib_Params& params) {
|
|||||||
|
|
||||||
const auto calib_temp_name = wxString::Format(L"Nozzle temperature test");
|
const auto calib_temp_name = wxString::Format(L"Nozzle temperature test");
|
||||||
new_project(false, false, calib_temp_name);
|
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 (params.mode != CalibMode::Calib_Temp_Tower) return;
|
||||||
|
|
||||||
if (!add_model(false, Slic3r::resources_dir() + "/calib/temperature_tower/temperature_tower.drc"))
|
if (!add_model(false, Slic3r::resources_dir() + "/calib/temperature_tower/temperature_tower.drc"))
|
||||||
@@ -14233,7 +14244,7 @@ void Plater::calib_max_vol_speed(const Calib_Params& params)
|
|||||||
{
|
{
|
||||||
const auto calib_vol_speed_name = wxString::Format(L"Max volumetric speed test");
|
const auto calib_vol_speed_name = wxString::Format(L"Max volumetric speed test");
|
||||||
new_project(false, false, calib_vol_speed_name);
|
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)
|
if (params.mode != CalibMode::Calib_Vol_speed_Tower)
|
||||||
return;
|
return;
|
||||||
if (!add_model(false, Slic3r::resources_dir() + "/calib/volumetric_speed/SpeedTestStructure.drc"))
|
if (!add_model(false, Slic3r::resources_dir() + "/calib/volumetric_speed/SpeedTestStructure.drc"))
|
||||||
@@ -14312,7 +14323,7 @@ void Plater::calib_retraction(const Calib_Params& params)
|
|||||||
{
|
{
|
||||||
const auto calib_retraction_name = wxString::Format(L"Retraction");
|
const auto calib_retraction_name = wxString::Format(L"Retraction");
|
||||||
new_project(false, false, calib_retraction_name);
|
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)
|
if (params.mode != CalibMode::Calib_Retraction_tower)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -14372,7 +14383,7 @@ void Plater::calib_VFA(const Calib_Params& params)
|
|||||||
{
|
{
|
||||||
const auto calib_vfa_name = wxString::Format(L"VFA test");
|
const auto calib_vfa_name = wxString::Format(L"VFA test");
|
||||||
new_project(false, false, calib_vfa_name);
|
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)
|
if (params.mode != CalibMode::Calib_VFA_Tower)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -14455,7 +14466,7 @@ void Plater::calib_input_shaping_freq(const Calib_Params& params)
|
|||||||
{
|
{
|
||||||
const auto calib_input_shaping_name = wxString::Format(L"Input shaping Frequency test");
|
const auto calib_input_shaping_name = wxString::Format(L"Input shaping Frequency test");
|
||||||
new_project(false, false, calib_input_shaping_name);
|
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)
|
if (params.mode != CalibMode::Calib_Input_shaping_freq)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -14521,7 +14532,7 @@ void Plater::calib_input_shaping_damp(const Calib_Params& params)
|
|||||||
{
|
{
|
||||||
const auto calib_input_shaping_name = wxString::Format(L"Input shaping Damping test");
|
const auto calib_input_shaping_name = wxString::Format(L"Input shaping Damping test");
|
||||||
new_project(false, false, calib_input_shaping_name);
|
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)
|
if (params.mode != CalibMode::Calib_Input_shaping_damp)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -14586,7 +14597,7 @@ void Plater::Calib_Cornering(const Calib_Params& params)
|
|||||||
{
|
{
|
||||||
const auto Calib_Cornering = wxString::Format(L"Cornering test");
|
const auto Calib_Cornering = wxString::Format(L"Cornering test");
|
||||||
new_project(false, false, Calib_Cornering);
|
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)
|
if (params.mode != CalibMode::Calib_Cornering)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -14719,7 +14730,7 @@ void Plater::load_gcode(const wxString& filename)
|
|||||||
//p->gcode_result.reset();
|
//p->gcode_result.reset();
|
||||||
//reset_gcode_toolpaths();
|
//reset_gcode_toolpaths();
|
||||||
p->preview->reload_print(m_only_gcode);
|
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->set_current_panel(p->preview, true);
|
||||||
p->get_current_canvas3D()->render();
|
p->get_current_canvas3D()->render();
|
||||||
//p->notification_manager->bbl_show_plateinfo_notification(into_u8(_L("Preview only mode for gcode file.")));
|
//p->notification_manager->bbl_show_plateinfo_notification(into_u8(_L("Preview only mode for gcode file.")));
|
||||||
@@ -15392,7 +15403,7 @@ LoadType determine_load_type(std::string filename, std::string override_setting)
|
|||||||
wxGetApp().app_config->set("import_project_action", std::to_string(choice));
|
wxGetApp().app_config->set("import_project_action", std::to_string(choice));
|
||||||
|
|
||||||
// BBS: jump to plater panel
|
// BBS: jump to plater panel
|
||||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||||
return load_type;
|
return load_type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -15621,7 +15632,7 @@ void Plater::reset_with_confirm()
|
|||||||
.ShowModal() == wxID_YES) {
|
.ShowModal() == wxID_YES) {
|
||||||
reset();
|
reset();
|
||||||
// BBS: jump to plater panel
|
// BBS: jump to plater panel
|
||||||
wxGetApp().mainframe->select_tab(size_t(0));
|
wxGetApp().mainframe->select_tab(TAB_ID_HOME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17435,7 +17446,7 @@ int Plater::export_config_3mf(int plate_idx, Export3mfProgressFn proFn)
|
|||||||
//BBS
|
//BBS
|
||||||
void Plater::send_calibration_job_finished(wxCommandEvent & evt)
|
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;
|
auto calibration_panel = p->main_frame->m_calibration;
|
||||||
if (calibration_panel) {
|
if (calibration_panel) {
|
||||||
auto curr_wizard = static_cast<CalibrationWizard*>(calibration_panel->get_tabpanel()->GetPage(evt.GetInt()));
|
auto curr_wizard = static_cast<CalibrationWizard*>(calibration_panel->get_tabpanel()->GetPage(evt.GetInt()));
|
||||||
@@ -17467,7 +17478,7 @@ void Plater::print_job_finished(wxCommandEvent &evt)
|
|||||||
if (!dev) return;
|
if (!dev) return;
|
||||||
|
|
||||||
dev->set_selected_machine(evt.GetString().ToStdString());
|
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
|
//jump to monitor and select device status panel
|
||||||
MonitorPanel* curr_monitor = p->main_frame->m_monitor;
|
MonitorPanel* curr_monitor = p->main_frame->m_monitor;
|
||||||
if(curr_monitor)
|
if(curr_monitor)
|
||||||
@@ -17482,7 +17493,7 @@ void Plater::send_job_finished(wxCommandEvent& evt)
|
|||||||
|
|
||||||
send_gcode_finish(evt.GetString());
|
send_gcode_finish(evt.GetString());
|
||||||
p->hide_send_to_printer_dlg();
|
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
|
////jump to monitor and select device status panel
|
||||||
//MonitorPanel* curr_monitor = p->main_frame->m_monitor;
|
//MonitorPanel* curr_monitor = p->main_frame->m_monitor;
|
||||||
//if (curr_monitor)
|
//if (curr_monitor)
|
||||||
@@ -18376,7 +18387,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"));
|
MessageDialog dlg(this, content, title, wxOK | wxFORWARD | wxICON_WARNING, _L("Device Page"));
|
||||||
auto result = dlg.ShowModal();
|
auto result = dlg.ShowModal();
|
||||||
if (result == wxFORWARD) {
|
if (result == wxFORWARD) {
|
||||||
wxGetApp().mainframe->select_tab(size_t(MainFrame::tpMonitor));
|
wxGetApp().mainframe->select_tab(TAB_ID_MONITOR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -290,7 +290,7 @@ public:
|
|||||||
Plater(const Plater &) = delete;
|
Plater(const Plater &) = delete;
|
||||||
Plater &operator=(Plater &&) = delete;
|
Plater &operator=(Plater &&) = delete;
|
||||||
Plater &operator=(const Plater &) = delete;
|
Plater &operator=(const Plater &) = delete;
|
||||||
~Plater() = default;
|
~Plater();
|
||||||
|
|
||||||
bool Show(bool show = true);
|
bool Show(bool show = true);
|
||||||
|
|
||||||
@@ -1020,4 +1020,4 @@ wxArrayString get_all_camera_view_type();
|
|||||||
} // namespace GUI
|
} // namespace GUI
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -15,39 +15,6 @@ namespace Slic3r { namespace GUI {
|
|||||||
|
|
||||||
namespace {
|
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 += "<style id=\"orca-plugin-defaults\">";
|
|
||||||
css += "html,body{background:var(--orca-bg);color:var(--orca-fg);"
|
|
||||||
"font-family:var(--orca-font);font-size:13px;}";
|
|
||||||
css += "body{margin:0;}";
|
|
||||||
css += "h1,h2,h3,h4,h5,h6{color:var(--orca-fg);font-weight:600;}";
|
|
||||||
css += "a{color:var(--orca-accent);}";
|
|
||||||
css += "hr{border:0;border-top:1px solid var(--orca-border);}";
|
|
||||||
css += "button{font:inherit;color:var(--orca-accent-fg);background:var(--orca-accent);"
|
|
||||||
"border:1px solid var(--orca-accent);border-radius:4px;padding:5px 14px;cursor:pointer;}";
|
|
||||||
css += "button:hover{filter:brightness(1.1);}";
|
|
||||||
css += "button:disabled{opacity:.5;cursor:default;}";
|
|
||||||
css += "input,select,textarea{font:inherit;color:var(--orca-fg);"
|
|
||||||
"background:var(--orca-bg);border:1px solid var(--orca-border);"
|
|
||||||
"border-radius:4px;padding:4px 8px;}";
|
|
||||||
css += "input:focus,select:focus,textarea:focus{outline:none;border-color:var(--orca-accent);}";
|
|
||||||
css += "table{border-collapse:collapse;}";
|
|
||||||
css += "th,td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--orca-border);}";
|
|
||||||
css += "th{color:var(--orca-muted);font-weight:600;}";
|
|
||||||
css += "::-webkit-scrollbar{width:12px;height:12px;}";
|
|
||||||
css += "::-webkit-scrollbar-thumb{background:var(--orca-border);border-radius:6px;}";
|
|
||||||
css += "::-webkit-scrollbar-track{background:transparent;}";
|
|
||||||
css += "</style>";
|
|
||||||
return WebViewHostDialog::document_start_injector(css, "orca-plugin-defaults", "beforeend");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Injected into the top-level page at document start (before the plugin's own
|
// 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
|
// 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
|
// 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()
|
void PluginWebDialog::add_user_scripts()
|
||||||
{
|
{
|
||||||
if (wxWebView* wv = browser()) {
|
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);
|
wv->AddUserScript(ORCA_BRIDGE_JS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1748,11 +1748,26 @@ void PreferencesDialog::create_items()
|
|||||||
g_sizer->Add(item_pop_up_filament_map_dialog);
|
g_sizer->Add(item_pop_up_filament_map_dialog);
|
||||||
#endif
|
#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));
|
g_sizer->AddSpacer(FromDIP(10));
|
||||||
sizer_page->Add(g_sizer, 0, wxEXPAND);
|
sizer_page->Add(g_sizer, 0, wxEXPAND);
|
||||||
|
|
||||||
//////////////////////////
|
//////////////////////////
|
||||||
//// CONTROL TAB
|
//// CONTROL TAB
|
||||||
/////////////////////////////////////
|
/////////////////////////////////////
|
||||||
m_pref_tabs->AppendItem(_L("Control"));
|
m_pref_tabs->AppendItem(_L("Control"));
|
||||||
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
|
f_sizers.push_back(new wxFlexGridSizer(1, 1, v_gap, 0));
|
||||||
|
|||||||
@@ -1042,7 +1042,7 @@ bool PlaterPresetComboBox::switch_to_tab()
|
|||||||
|
|
||||||
//BBS Select NoteBook Tab params
|
//BBS Select NoteBook Tab params
|
||||||
if (tab->GetParent() == wxGetApp().params_panel())
|
if (tab->GetParent() == wxGetApp().params_panel())
|
||||||
wxGetApp().mainframe->select_tab(MainFrame::tp3DEditor);
|
wxGetApp().mainframe->select_tab(TAB_ID_PREPARE);
|
||||||
else {
|
else {
|
||||||
wxGetApp().params_dialog()->Popup();
|
wxGetApp().params_dialog()->Popup();
|
||||||
tab->OnActivate();
|
tab->OnActivate();
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ public:
|
|||||||
PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size = wxDefaultSize, PresetBundle* preset_bundle = nullptr);
|
PresetComboBox(wxWindow* parent, Preset::Type preset_type, const wxSize& size = wxDefaultSize, PresetBundle* preset_bundle = nullptr);
|
||||||
~PresetComboBox();
|
~PresetComboBox();
|
||||||
|
|
||||||
enum LabelItemType {
|
enum LabelItemType : std::size_t {
|
||||||
LABEL_ITEM_PHYSICAL_PRINTER = 0xffffff01,
|
LABEL_ITEM_PHYSICAL_PRINTER = 0xffffff01,
|
||||||
LABEL_ITEM_PRINTER_MODELS,
|
LABEL_ITEM_PRINTER_MODELS,
|
||||||
LABEL_ITEM_DISABLED,
|
LABEL_ITEM_DISABLED,
|
||||||
|
|||||||
+51
-10
@@ -74,7 +74,18 @@ ProjectPanel::ProjectPanel(wxWindow *parent, wxWindowID id, const wxPoint &pos,
|
|||||||
Fit();
|
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 <br>
|
// Helper to convert newlines to <br>
|
||||||
static std::string convert_newlines_to_br(const std::string& text) {
|
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)
|
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<boost::thread>([this, cancel_token] {
|
||||||
|
if (cancel_token->load(std::memory_order_acquire) || wxTheApp == nullptr || wxGetApp().is_closing())
|
||||||
|
return;
|
||||||
std::string update_type;
|
std::string update_type;
|
||||||
std::string license;
|
std::string license;
|
||||||
std::string model_name;
|
std::string model_name;
|
||||||
@@ -115,6 +136,9 @@ void ProjectPanel::on_reload(wxCommandEvent& evt)
|
|||||||
|
|
||||||
std::map<std::string, std::vector<json>> files;
|
std::map<std::string, std::vector<json>> files;
|
||||||
|
|
||||||
|
if (wxGetApp().plater() == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
Model model = wxGetApp().plater()->model();
|
Model model = wxGetApp().plater()->model();
|
||||||
|
|
||||||
auto model_info = model.model_info;
|
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());
|
std::string file_path = encode_path(wxGetApp().plater()->model().get_auxiliary_file_temp_path().c_str());
|
||||||
if (!file_path.empty()) {
|
if (!file_path.empty()) {
|
||||||
files = Reload(file_path);
|
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 {
|
} else {
|
||||||
clear_model_info();
|
clear_model_info();
|
||||||
return;
|
return;
|
||||||
@@ -215,15 +246,18 @@ void ProjectPanel::on_reload(wxCommandEvent& evt)
|
|||||||
|
|
||||||
json m_Res = json::object();
|
json m_Res = json::object();
|
||||||
m_Res["command"] = "show_3mf_info";
|
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;
|
m_Res["model"] = j;
|
||||||
|
|
||||||
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore));
|
wxString strJS = wxString::Format("HandleStudio(%s)", m_Res.dump(-1, ' ', false, json::error_handler_t::ignore));
|
||||||
|
|
||||||
if (m_web_init_completed) {
|
if (m_web_init_completed.load(std::memory_order_acquire) &&
|
||||||
wxGetApp().CallAfter([this, strJS] {
|
!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());
|
RunScript(strJS.ToStdString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -264,7 +298,7 @@ void ProjectPanel::OnScriptMessage(wxWebViewEvent& evt)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (strCmd == "request_3mf_info") {
|
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") {
|
else if (strCmd == "edit_project_info") {
|
||||||
show_info_editor(true);
|
show_info_editor(true);
|
||||||
@@ -307,13 +341,20 @@ void ProjectPanel::update_model_data()
|
|||||||
|
|
||||||
void ProjectPanel::clear_model_info()
|
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();
|
json m_Res = json::object();
|
||||||
m_Res["command"] = "clear_3mf_info";
|
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));
|
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());
|
RunScript(strJS.ToStdString());
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,9 +26,11 @@
|
|||||||
#include "nlohmann/json.hpp"
|
#include "nlohmann/json.hpp"
|
||||||
#include "slic3r/Utils/json_diff.hpp"
|
#include "slic3r/Utils/json_diff.hpp"
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <boost/thread.hpp>
|
||||||
#include "Event.hpp"
|
#include "Event.hpp"
|
||||||
#include "libslic3r/ProjectTask.hpp"
|
#include "libslic3r/ProjectTask.hpp"
|
||||||
#include "wxExtensions.hpp"
|
#include "wxExtensions.hpp"
|
||||||
@@ -60,14 +62,17 @@ struct project_file{
|
|||||||
class ProjectPanel : public wxPanel
|
class ProjectPanel : public wxPanel
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
bool m_web_init_completed = {false};
|
std::atomic<bool> m_web_init_completed{false};
|
||||||
bool m_reload_already = {false};
|
bool m_reload_already = {false};
|
||||||
|
|
||||||
|
std::shared_ptr<std::atomic<bool>> m_reload_cancel_token{std::make_shared<std::atomic<bool>>(false)};
|
||||||
|
std::unique_ptr<boost::thread> m_reload_task;
|
||||||
|
|
||||||
wxWebView* m_browser = {nullptr};
|
wxWebView* m_browser = {nullptr};
|
||||||
AuxiliaryPanel* m_auxiliary{nullptr};
|
AuxiliaryPanel* m_auxiliary{nullptr};
|
||||||
wxString m_project_home_url;
|
wxString m_project_home_url;
|
||||||
wxString m_root_dir;
|
wxString m_root_dir;
|
||||||
static inline int m_sequence_id = 8000;
|
static inline std::atomic<int> m_sequence_id{8000};
|
||||||
|
|
||||||
void show_info_editor(bool show);
|
void show_info_editor(bool show);
|
||||||
|
|
||||||
@@ -75,6 +80,7 @@ private:
|
|||||||
public:
|
public:
|
||||||
ProjectPanel(wxWindow *parent, wxWindowID id = wxID_ANY, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
|
ProjectPanel(wxWindow *parent, wxWindowID id = wxID_ANY, const wxPoint &pos = wxDefaultPosition, const wxSize &size = wxDefaultSize, long style = wxTAB_TRAVERSAL);
|
||||||
~ProjectPanel();
|
~ProjectPanel();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
|
||||||
void onWebNavigating(wxWebViewEvent& evt);
|
void onWebNavigating(wxWebViewEvent& evt);
|
||||||
|
|||||||
@@ -1088,8 +1088,8 @@ void SelectMachineDialog::sync_ams_mapping_result(std::vector<FilamentInfo> &res
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
relayout_nozzle_cards();
|
relayout_nozzle_cards();
|
||||||
auto tab_index = (MainFrame::TabPosition) dynamic_cast<Notebook *>(wxGetApp().tab_panel())->GetSelection();
|
wxString tab_name = wxGetApp().tab_panel()->GetSelectedPageName();
|
||||||
if (tab_index == MainFrame::TabPosition::tp3DEditor || tab_index == MainFrame::TabPosition::tpPreview) {
|
if (tab_name == TAB_ID_PREPARE || tab_name == TAB_ID_PREVIEW) {
|
||||||
updata_thumbnail_data_after_connected_printer();
|
updata_thumbnail_data_after_connected_printer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1218,8 +1218,8 @@ void SyncAmsInfoDialog::sync_ams_mapping_result(std::vector<FilamentInfo> &resul
|
|||||||
iter++;
|
iter++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto tab_index = (MainFrame::TabPosition) dynamic_cast<Notebook *>(wxGetApp().tab_panel())->GetSelection();
|
wxString tab_name = wxGetApp().tab_panel()->GetSelectedPageName();
|
||||||
if (tab_index == MainFrame::TabPosition::tp3DEditor || tab_index == MainFrame::TabPosition::tpPreview) {
|
if (tab_name == TAB_ID_PREPARE || tab_name == TAB_ID_PREVIEW) {
|
||||||
updata_thumbnail_data_after_connected_printer();
|
updata_thumbnail_data_after_connected_printer();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2790,7 +2790,7 @@ void TabPrint::build()
|
|||||||
optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline");
|
optgroup->append_single_option_line("fill_multiline", "strength_settings_infill#fill-multiline");
|
||||||
optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern");
|
optgroup->append_single_option_line("sparse_infill_pattern", "strength_settings_infill#sparse-infill-pattern");
|
||||||
optgroup->append_single_option_line("gyroid_optimized", "strength_settings_patterns#gyroid-optimized");
|
optgroup->append_single_option_line("gyroid_optimized", "strength_settings_patterns#gyroid-optimized");
|
||||||
optgroup->append_single_option_line("sparse_infill_smooth_factor", "strength_settings_patterns#sparse-infill-smooth-factor");
|
optgroup->append_single_option_line("sparse_infill_smooth_factor", "strength_settings_infill#sparse-infill-smooth-factor");
|
||||||
optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction");
|
optgroup->append_single_option_line("infill_direction", "strength_settings_infill#direction");
|
||||||
optgroup->append_single_option_line("sparse_infill_rotate_template", "strength_settings_infill_rotation_template_metalanguage");
|
optgroup->append_single_option_line("sparse_infill_rotate_template", "strength_settings_infill_rotation_template_metalanguage");
|
||||||
optgroup->append_single_option_line("skin_infill_density", "strength_settings_patterns#locked-zag");
|
optgroup->append_single_option_line("skin_infill_density", "strength_settings_patterns#locked-zag");
|
||||||
@@ -6458,7 +6458,7 @@ void Tab::load_current_preset()
|
|||||||
std::string bmp_name = tab->type() == Slic3r::Preset::TYPE_FILAMENT ? "spool" :
|
std::string bmp_name = tab->type() == Slic3r::Preset::TYPE_FILAMENT ? "spool" :
|
||||||
tab->type() == Slic3r::Preset::TYPE_SLA_MATERIAL ? "" : "cog";
|
tab->type() == Slic3r::Preset::TYPE_SLA_MATERIAL ? "" : "cog";
|
||||||
tab->Hide(); // #ys_WORKAROUND : Hide tab before inserting to avoid unwanted rendering of the tab
|
tab->Hide(); // #ys_WORKAROUND : Hide tab before inserting to avoid unwanted rendering of the tab
|
||||||
dynamic_cast<Notebook*>(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), tab, tab->title(), bmp_name);
|
dynamic_cast<Notebook*>(wxGetApp().tab_panel())->InsertPage(wxGetApp().tab_panel()->FindPage(this), wxString(), tab, tab->title(), bmp_name);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
@@ -8537,8 +8537,12 @@ void Page::activate(ConfigOptionMode mode, std::function<void()> throw_if_cancel
|
|||||||
|
|
||||||
#ifdef __WXMSW__
|
#ifdef __WXMSW__
|
||||||
// BBS: fix field control position
|
// BBS: fix field control position
|
||||||
wxTheApp->CallAfter([this]() {
|
wxTheApp->CallAfter([wp = std::weak_ptr<Page>(shared_from_this())]() {
|
||||||
for (auto group : m_optgroups) {
|
auto page = wp.lock();
|
||||||
|
if (!page)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (auto group : page->m_optgroups) {
|
||||||
if (group->custom_ctrl)
|
if (group->custom_ctrl)
|
||||||
group->custom_ctrl->fixup_items_positions();
|
group->custom_ctrl->fixup_items_positions();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ public:
|
|||||||
// by this control) and show it immediately.
|
// by this control) and show it immediately.
|
||||||
bool ShowNewPage(wxWindow * page)
|
bool ShowNewPage(wxWindow * page)
|
||||||
{
|
{
|
||||||
return AddPage(page, wxString(), ""/*true *//* select it */);
|
return AddPage(page, wxString());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set effect to use for showing/hiding pages.
|
// Set effect to use for showing/hiding pages.
|
||||||
@@ -139,14 +139,13 @@ public:
|
|||||||
|
|
||||||
// Implement base class pure virtual methods.
|
// Implement base class pure virtual methods.
|
||||||
|
|
||||||
// adds a new page to the control
|
|
||||||
bool AddPage(wxWindow* page,
|
bool AddPage(wxWindow* page,
|
||||||
const wxString& text,
|
const wxString& text,
|
||||||
const std::string& bmp_name,
|
bool bSelect = false,
|
||||||
bool bSelect = false)
|
int imageId = NO_IMAGE) override
|
||||||
{
|
{
|
||||||
DoInvalidateBestSize();
|
DoInvalidateBestSize();
|
||||||
return InsertNewPage(GetPageCount(), page, text, bmp_name, bSelect);
|
return InsertPage(GetPageCount(), page, text, bSelect, imageId);
|
||||||
}
|
}
|
||||||
|
|
||||||
//// Page management
|
//// Page management
|
||||||
@@ -167,23 +166,6 @@ public:
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool InsertNewPage(size_t n,
|
|
||||||
wxWindow * page,
|
|
||||||
const wxString & text,
|
|
||||||
const std::string& bmp_name = "",
|
|
||||||
bool bSelect = false)
|
|
||||||
{
|
|
||||||
if (!wxBookCtrlBase::InsertPage(n, page, text, bSelect))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
GetBtnsListCtrl()->InsertPage(n, text, bSelect, bmp_name);
|
|
||||||
|
|
||||||
if (bSelect)
|
|
||||||
SetSelection(n);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RemovePage(size_t n)
|
bool RemovePage(size_t n)
|
||||||
{
|
{
|
||||||
if (!wxBookCtrlBase::RemovePage(n))
|
if (!wxBookCtrlBase::RemovePage(n))
|
||||||
|
|||||||
@@ -95,14 +95,11 @@ void Button::SetIcon(const wxString& icon)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Button::SetInactiveIcon(const wxString &icon)
|
void Button::SetIcon(const wxBitmap& icon)
|
||||||
{
|
{
|
||||||
if (!icon.IsEmpty()) {
|
this->active_icon = ScalableBitmap();
|
||||||
// BBS set button icon default size to 20
|
this->active_icon.bmp() = icon;
|
||||||
this->inactive_icon = ScalableBitmap(this, icon.ToStdString(), this->active_icon.px_cnt());
|
messureSize();
|
||||||
} else {
|
|
||||||
this->inactive_icon = ScalableBitmap();
|
|
||||||
}
|
|
||||||
Refresh();
|
Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,12 +254,10 @@ void Button::SetStyle(const ButtonStyle style, const ButtonType type)
|
|||||||
|
|
||||||
void Button::Rescale()
|
void Button::Rescale()
|
||||||
{
|
{
|
||||||
if (this->active_icon.bmp().IsOk())
|
// Only a named icon can be re-rasterized; one set from a wxBitmap has no source file,
|
||||||
|
if (!this->active_icon.name().empty())
|
||||||
this->active_icon.msw_rescale();
|
this->active_icon.msw_rescale();
|
||||||
|
|
||||||
if (this->inactive_icon.bmp().IsOk())
|
|
||||||
this->inactive_icon.msw_rescale();
|
|
||||||
|
|
||||||
messureSize();
|
messureSize();
|
||||||
|
|
||||||
if(m_has_style)
|
if(m_has_style)
|
||||||
@@ -293,11 +288,7 @@ void Button::render(wxDC& dc)
|
|||||||
wxSize szIcon;
|
wxSize szIcon;
|
||||||
wxSize textSize = this->textSize.GetSize();
|
wxSize textSize = this->textSize.GetSize();
|
||||||
|
|
||||||
ScalableBitmap icon;
|
const ScalableBitmap& icon = active_icon;
|
||||||
if (m_selected || ((states & (int)StateColor::State::Hovered) != 0))
|
|
||||||
icon = active_icon;
|
|
||||||
else
|
|
||||||
icon = inactive_icon;
|
|
||||||
wxSize padding = this->paddingSize;
|
wxSize padding = this->paddingSize;
|
||||||
int spacing = 5;
|
int spacing = 5;
|
||||||
// Wrap text
|
// Wrap text
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ class Button : public StaticBox
|
|||||||
wxSize minSize; // set by outer
|
wxSize minSize; // set by outer
|
||||||
wxSize paddingSize;
|
wxSize paddingSize;
|
||||||
ScalableBitmap active_icon;
|
ScalableBitmap active_icon;
|
||||||
ScalableBitmap inactive_icon;
|
|
||||||
|
|
||||||
StateColor text_color;
|
StateColor text_color;
|
||||||
|
|
||||||
@@ -61,8 +60,7 @@ public:
|
|||||||
bool SetFont(const wxFont& font) override;
|
bool SetFont(const wxFont& font) override;
|
||||||
|
|
||||||
void SetIcon(const wxString& icon);
|
void SetIcon(const wxString& icon);
|
||||||
|
void SetIcon(const wxBitmap& icon);
|
||||||
void SetInactiveIcon(const wxString& icon);
|
|
||||||
|
|
||||||
void SetMinSize(const wxSize& size) override;
|
void SetMinSize(const wxSize& size) override;
|
||||||
void SetMaxSize(const wxSize& size) override;
|
void SetMaxSize(const wxSize& size) override;
|
||||||
|
|||||||
@@ -57,18 +57,6 @@ std::string host_theme_vars_css()
|
|||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Document-start user script: injects the contract <style>, stamps data-orca-theme before
|
|
||||||
// first paint, and raises a JS flag so the legacy globalapi.js dark.css poll stands down for
|
|
||||||
// host-themed pages. The WebView2 timing guard lives in document_start_injector().
|
|
||||||
std::string host_theme_user_script()
|
|
||||||
{
|
|
||||||
const std::string style = "<style id=\"orca-host-theme-vars\">" + host_theme_vars_css() + "</style>";
|
|
||||||
return WebViewHostDialog::document_start_injector(
|
|
||||||
style, "orca-host-theme-vars", "afterbegin",
|
|
||||||
"window.__orcaHostThemed=true;var theme=\"" + host_theme_name() + "\";",
|
|
||||||
"if(document.documentElement)document.documentElement.setAttribute('data-orca-theme',theme);");
|
|
||||||
}
|
|
||||||
|
|
||||||
// JS to re-theme an already-loaded document live (no reload): replace the injected
|
// JS to re-theme an already-loaded document live (no reload): replace the injected
|
||||||
// style's contents and update data-orca-theme. Everything downstream (theme.css
|
// style's contents and update data-orca-theme. Everything downstream (theme.css
|
||||||
// tokens, plugin element defaults, page layout) re-cascades from these values.
|
// tokens, plugin element defaults, page layout) re-cascades from these values.
|
||||||
@@ -87,6 +75,46 @@ if(document.documentElement)
|
|||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
|
// Document-start user script: injects the contract <style>, stamps data-orca-theme before
|
||||||
|
// first paint, and raises a JS flag so the legacy globalapi.js dark.css poll stands down for
|
||||||
|
// host-themed pages. The WebView2 timing guard lives in document_start_injector().
|
||||||
|
std::string WebViewHostDialog::theme_user_script()
|
||||||
|
{
|
||||||
|
const std::string style = "<style id=\"orca-host-theme-vars\">" + host_theme_vars_css() + "</style>";
|
||||||
|
return document_start_injector(
|
||||||
|
style, "orca-host-theme-vars", "afterbegin",
|
||||||
|
"window.__orcaHostThemed=true;var theme=\"" + host_theme_name() + "\";",
|
||||||
|
"if(document.documentElement)document.documentElement.setAttribute('data-orca-theme',theme);");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string WebViewHostDialog::plugin_defaults_user_script()
|
||||||
|
{
|
||||||
|
std::string css;
|
||||||
|
css += "<style id=\"orca-plugin-defaults\">";
|
||||||
|
css += "html,body{background:var(--orca-bg);color:var(--orca-fg);"
|
||||||
|
"font-family:var(--orca-font);font-size:13px;}";
|
||||||
|
css += "body{margin:0;}";
|
||||||
|
css += "h1,h2,h3,h4,h5,h6{color:var(--orca-fg);font-weight:600;}";
|
||||||
|
css += "a{color:var(--orca-accent);}";
|
||||||
|
css += "hr{border:0;border-top:1px solid var(--orca-border);}";
|
||||||
|
css += "button{font:inherit;color:var(--orca-accent-fg);background:var(--orca-accent);"
|
||||||
|
"border:1px solid var(--orca-accent);border-radius:4px;padding:5px 14px;cursor:pointer;}";
|
||||||
|
css += "button:hover{filter:brightness(1.1);}";
|
||||||
|
css += "button:disabled{opacity:.5;cursor:default;}";
|
||||||
|
css += "input,select,textarea{font:inherit;color:var(--orca-fg);"
|
||||||
|
"background:var(--orca-bg);border:1px solid var(--orca-border);"
|
||||||
|
"border-radius:4px;padding:4px 8px;}";
|
||||||
|
css += "input:focus,select:focus,textarea:focus{outline:none;border-color:var(--orca-accent);}";
|
||||||
|
css += "table{border-collapse:collapse;}";
|
||||||
|
css += "th,td{text-align:left;padding:6px 10px;border-bottom:1px solid var(--orca-border);}";
|
||||||
|
css += "th{color:var(--orca-muted);font-weight:600;}";
|
||||||
|
css += "::-webkit-scrollbar{width:12px;height:12px;}";
|
||||||
|
css += "::-webkit-scrollbar-thumb{background:var(--orca-border);border-radius:6px;}";
|
||||||
|
css += "::-webkit-scrollbar-track{background:transparent;}";
|
||||||
|
css += "</style>";
|
||||||
|
return document_start_injector(css, "orca-plugin-defaults", "beforeend");
|
||||||
|
}
|
||||||
|
|
||||||
std::string WebViewHostDialog::document_start_injector(const std::string& markup,
|
std::string WebViewHostDialog::document_start_injector(const std::string& markup,
|
||||||
const char* dom_id,
|
const char* dom_id,
|
||||||
const char* position,
|
const char* position,
|
||||||
@@ -244,7 +272,7 @@ void WebViewHostDialog::register_theme_user_scripts()
|
|||||||
// script message handler is registered separately (AddScriptMessageHandler), but on
|
// script message handler is registered separately (AddScriptMessageHandler), but on
|
||||||
// some backends RemoveAllUserScripts() drops it too, which would break
|
// some backends RemoveAllUserScripts() drops it too, which would break
|
||||||
// window.wx.postMessage / HandleStudio. Live re-theme goes through apply_theme_live().
|
// window.wx.postMessage / HandleStudio. Live re-theme goes through apply_theme_live().
|
||||||
m_browser->AddUserScript(wxString::FromUTF8(host_theme_user_script()));
|
m_browser->AddUserScript(wxString::FromUTF8(theme_user_script()));
|
||||||
add_user_scripts();
|
add_user_scripts();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,10 @@ public:
|
|||||||
const std::string& prelude = {},
|
const std::string& prelude = {},
|
||||||
const std::string& on_inject = {});
|
const std::string& on_inject = {});
|
||||||
|
|
||||||
|
// Shared by modeless Pages tabs and PluginWebDialog.
|
||||||
|
static std::string theme_user_script();
|
||||||
|
static std::string plugin_defaults_user_script();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
wxWebView* browser() const { return m_browser; }
|
wxWebView* browser() const { return m_browser; }
|
||||||
|
|
||||||
|
|||||||
@@ -368,7 +368,7 @@ void PrintHostJobQueue::priv::perform_job(PrintHostJob the_job)
|
|||||||
emit_progress(100);
|
emit_progress(100);
|
||||||
if (the_job.switch_to_device_tab) {
|
if (the_job.switch_to_device_tab) {
|
||||||
const auto mainframe = GUI::wxGetApp().mainframe;
|
const auto mainframe = GUI::wxGetApp().mainframe;
|
||||||
mainframe->request_select_tab(MainFrame::TabPosition::tpMonitor);
|
mainframe->request_select_tab(TAB_ID_MONITOR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -325,7 +325,7 @@ bool SimplyPrint::do_temp_upload(const boost::filesystem::path& file_path,
|
|||||||
wxLaunchDefaultBrowser(url);
|
wxLaunchDefaultBrowser(url);
|
||||||
} else {
|
} else {
|
||||||
const auto mainframe = GUI::wxGetApp().mainframe;
|
const auto mainframe = GUI::wxGetApp().mainframe;
|
||||||
mainframe->request_select_tab(MainFrame::TabPosition::tpMonitor);
|
mainframe->request_select_tab(TAB_ID_MONITOR);
|
||||||
mainframe->load_printer_url(url);
|
mainframe->load_printer_url(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@
|
|||||||
#include "PyPluginPackage.hpp"
|
#include "PyPluginPackage.hpp"
|
||||||
#include "PyPluginTrampoline.hpp"
|
#include "PyPluginTrampoline.hpp"
|
||||||
#include "pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp"
|
#include "pluginTypes/printerAgent/PrinterAgentPluginCapability.hpp"
|
||||||
|
#include "pluginTypes/pages/PagesPluginCapability.hpp"
|
||||||
#include "pluginTypes/script/ScriptPluginCapability.hpp"
|
#include "pluginTypes/script/ScriptPluginCapability.hpp"
|
||||||
#include "pluginTypes/slicingPipeline/SlicingPipelinePluginCapability.hpp"
|
#include "pluginTypes/slicingPipeline/SlicingPipelinePluginCapability.hpp"
|
||||||
|
|
||||||
@@ -331,17 +332,17 @@ void bind_python_api(pybind11::module_& m)
|
|||||||
{
|
{
|
||||||
m.doc() = "OrcaSlicer plugin API";
|
m.doc() = "OrcaSlicer plugin API";
|
||||||
|
|
||||||
auto pluginTypes = py::enum_<PluginCapabilityType>(m, "PluginType", "Available plugin capability groups")
|
py::enum_<PluginCapabilityType>(m, "PluginType", "Available plugin capability groups")
|
||||||
.value("PrinterConnection", PluginCapabilityType::PrinterConnection)
|
.value("PrinterConnection", PluginCapabilityType::PrinterConnection)
|
||||||
.value("Automation", PluginCapabilityType::Automation)
|
.value("Pages", PluginCapabilityType::Pages)
|
||||||
.value("Analysis", PluginCapabilityType::Analysis)
|
.value("Analysis", PluginCapabilityType::Analysis)
|
||||||
.value("Importer", PluginCapabilityType::Importer)
|
.value("Importer", PluginCapabilityType::Importer)
|
||||||
.value("Exporter", PluginCapabilityType::Exporter)
|
.value("Exporter", PluginCapabilityType::Exporter)
|
||||||
.value("Visualization", PluginCapabilityType::Visualization)
|
.value("Visualization", PluginCapabilityType::Visualization)
|
||||||
.value("Script", PluginCapabilityType::Script)
|
.value("Script", PluginCapabilityType::Script)
|
||||||
.value("SlicingPipeline", PluginCapabilityType::SlicingPipeline)
|
.value("SlicingPipeline", PluginCapabilityType::SlicingPipeline)
|
||||||
.value("Unknown", PluginCapabilityType::Unknown)
|
.value("Unknown", PluginCapabilityType::Unknown)
|
||||||
.export_values();
|
.export_values();
|
||||||
|
|
||||||
py::enum_<PluginResult>(m, "PluginResult", "Execution summary code")
|
py::enum_<PluginResult>(m, "PluginResult", "Execution summary code")
|
||||||
.value("Success", PluginResult::Success)
|
.value("Success", PluginResult::Success)
|
||||||
@@ -431,9 +432,10 @@ void bind_python_api(pybind11::module_& m)
|
|||||||
BOOST_LOG_TRIVIAL(debug) << "Registering embedded Python plugin type bindings";
|
BOOST_LOG_TRIVIAL(debug) << "Registering embedded Python plugin type bindings";
|
||||||
|
|
||||||
// Make sure you register your bindings here
|
// Make sure you register your bindings here
|
||||||
PrinterAgentPluginCapability::RegisterBindings(m, pluginTypes);
|
PrinterAgentPluginCapability::RegisterBindings(m);
|
||||||
ScriptPluginCapability::RegisterBindings(m, pluginTypes);
|
PagesPluginCapability::RegisterBindings(m);
|
||||||
SlicingPipelinePluginCapability::RegisterBindings(m, pluginTypes);
|
ScriptPluginCapability::RegisterBindings(m);
|
||||||
|
SlicingPipelinePluginCapability::RegisterBindings(m);
|
||||||
PluginHost::RegisterBindings(m);
|
PluginHost::RegisterBindings(m);
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Registered ScriptPluginCapability Python bindings";
|
BOOST_LOG_TRIVIAL(debug) << "Registered ScriptPluginCapability Python bindings";
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
enum class PluginCapabilityType { PrinterConnection = 0, Automation, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, Unknown };
|
enum class PluginCapabilityType { PrinterConnection = 0, Pages, Analysis, Importer, Exporter, Visualization, Script, SlicingPipeline, Unknown };
|
||||||
|
|
||||||
struct PluginCapabilityId
|
struct PluginCapabilityId
|
||||||
{
|
{
|
||||||
@@ -39,7 +39,7 @@ inline std::string plugin_capability_type_to_string(PluginCapabilityType type)
|
|||||||
{
|
{
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PluginCapabilityType::PrinterConnection: return "printer-connection";
|
case PluginCapabilityType::PrinterConnection: return "printer-connection";
|
||||||
case PluginCapabilityType::Automation: return "automation";
|
case PluginCapabilityType::Pages: return "pages";
|
||||||
case PluginCapabilityType::Analysis: return "analysis";
|
case PluginCapabilityType::Analysis: return "analysis";
|
||||||
case PluginCapabilityType::Importer: return "importer";
|
case PluginCapabilityType::Importer: return "importer";
|
||||||
case PluginCapabilityType::Exporter: return "exporter";
|
case PluginCapabilityType::Exporter: return "exporter";
|
||||||
@@ -54,7 +54,7 @@ inline std::string plugin_capability_type_display_name(PluginCapabilityType type
|
|||||||
{
|
{
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case PluginCapabilityType::PrinterConnection: return "Printer connection";
|
case PluginCapabilityType::PrinterConnection: return "Printer connection";
|
||||||
case PluginCapabilityType::Automation: return "Automation";
|
case PluginCapabilityType::Pages: return "Pages";
|
||||||
case PluginCapabilityType::Analysis: return "Analysis";
|
case PluginCapabilityType::Analysis: return "Analysis";
|
||||||
case PluginCapabilityType::Importer: return "Importer";
|
case PluginCapabilityType::Importer: return "Importer";
|
||||||
case PluginCapabilityType::Exporter: return "Exporter";
|
case PluginCapabilityType::Exporter: return "Exporter";
|
||||||
@@ -76,8 +76,8 @@ inline PluginCapabilityType plugin_capability_type_from_string(std::string_view
|
|||||||
|
|
||||||
if (lowered == "printer-connection")
|
if (lowered == "printer-connection")
|
||||||
return PluginCapabilityType::PrinterConnection;
|
return PluginCapabilityType::PrinterConnection;
|
||||||
if (lowered == "automation")
|
if (lowered == "pages")
|
||||||
return PluginCapabilityType::Automation;
|
return PluginCapabilityType::Pages;
|
||||||
if (lowered == "analysis")
|
if (lowered == "analysis")
|
||||||
return PluginCapabilityType::Analysis;
|
return PluginCapabilityType::Analysis;
|
||||||
if (lowered == "importer")
|
if (lowered == "importer")
|
||||||
|
|||||||
@@ -0,0 +1,469 @@
|
|||||||
|
#include "PluginPages.hpp"
|
||||||
|
|
||||||
|
#include "libslic3r/AppConfig.hpp"
|
||||||
|
#include "slic3r/GUI/GUI.hpp"
|
||||||
|
#include "slic3r/GUI/Notebook.hpp"
|
||||||
|
#include "slic3r/GUI/GUI_App.hpp"
|
||||||
|
#include "slic3r/GUI/Widgets/Button.hpp"
|
||||||
|
#include "slic3r/GUI/Widgets/WebView.hpp"
|
||||||
|
#include "slic3r/GUI/Widgets/WebViewHostDialog.hpp"
|
||||||
|
#include "slic3r/GUI/wxExtensions.hpp"
|
||||||
|
#include "slic3r/plugin/PluginManager.hpp"
|
||||||
|
|
||||||
|
#include <libslic3r/Utils.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <boost/filesystem/path.hpp>
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
#include <stdexcept>
|
||||||
|
#include <wx/bookctrl.h>
|
||||||
|
#include <wx/menu.h>
|
||||||
|
#include <wx/sizer.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr char PLUGIN_PAGE_BRIDGE_JS[] = R"JS(
|
||||||
|
(function () {
|
||||||
|
if (window.top !== window.self) return;
|
||||||
|
if (window.orca) return;
|
||||||
|
var handlers = [];
|
||||||
|
function deliver(payload, attempts) {
|
||||||
|
try {
|
||||||
|
if (window.wx && typeof window.wx.postMessage === 'function') {
|
||||||
|
window.wx.postMessage(payload);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} catch (e) { /* retry while the native handler is being registered */ }
|
||||||
|
if (attempts < 100)
|
||||||
|
window.setTimeout(function () { deliver(payload, attempts + 1); }, 25);
|
||||||
|
}
|
||||||
|
function send(data) {
|
||||||
|
deliver(JSON.stringify({
|
||||||
|
channel: 'orca', kind: 'message', data: (data === undefined ? null : data)
|
||||||
|
}), 0);
|
||||||
|
}
|
||||||
|
window.orca = {
|
||||||
|
postMessage: function (data) { send(data); },
|
||||||
|
onMessage: function (callback) {
|
||||||
|
if (typeof callback === 'function') handlers.push(callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
window.__orcaDispatch = function (payload) {
|
||||||
|
var data = payload ? payload.data : null;
|
||||||
|
for (var i = 0; i < handlers.length; i++) {
|
||||||
|
try { handlers[i](data); } catch (e) {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
)JS";
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
PluginPage::PluginPage(wxWindow* parent, std::shared_ptr<PagesPluginCapability> capability)
|
||||||
|
: wxPanel(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize)
|
||||||
|
, m_cap(std::move(capability))
|
||||||
|
, m_lifetime(std::make_shared<std::atomic<PluginPage*>>(this))
|
||||||
|
{
|
||||||
|
auto* topsizer = new wxBoxSizer(wxVERTICAL);
|
||||||
|
SetSizer(topsizer);
|
||||||
|
|
||||||
|
m_browser = WebView::CreateWebView(this, bootstrap_url());
|
||||||
|
if (m_browser == nullptr) {
|
||||||
|
wxLogError("Could not initialize plugin page web view");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
topsizer->Add(m_browser, wxSizerFlags().Expand().Proportion(1));
|
||||||
|
m_browser->Bind(wxEVT_WEBVIEW_LOADED, &PluginPage::on_bootstrap_event, this);
|
||||||
|
m_browser->Bind(wxEVT_WEBVIEW_ERROR, &PluginPage::on_bootstrap_event, this);
|
||||||
|
m_browser->Bind(wxEVT_WEBVIEW_NEWWINDOW, &PluginPage::on_new_window, this);
|
||||||
|
m_browser->Bind(wxEVT_WEBVIEW_SCRIPT_MESSAGE_RECEIVED, &PluginPage::on_script_message, this);
|
||||||
|
m_browser->AddUserScript(wxString::FromUTF8(GUI::WebViewHostDialog::theme_user_script()));
|
||||||
|
m_browser->AddUserScript(wxString::FromUTF8(GUI::WebViewHostDialog::plugin_defaults_user_script()));
|
||||||
|
m_browser->AddUserScript(PLUGIN_PAGE_BRIDGE_JS);
|
||||||
|
|
||||||
|
const std::shared_ptr<std::atomic<PluginPage*>> lifetime = m_lifetime;
|
||||||
|
m_cap->set_message_sender([lifetime](const std::string& message) {
|
||||||
|
if (wxTheApp == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GUI::wxGetApp().CallAfter([lifetime, message] {
|
||||||
|
if (PluginPage* page = lifetime->load(std::memory_order_acquire))
|
||||||
|
page->push_message(message);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginPage::~PluginPage()
|
||||||
|
{
|
||||||
|
detach_capability();
|
||||||
|
if (m_lifetime)
|
||||||
|
m_lifetime->store(nullptr, std::memory_order_release);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPage::detach_capability()
|
||||||
|
{
|
||||||
|
if (m_lifetime)
|
||||||
|
m_lifetime->store(nullptr, std::memory_order_release);
|
||||||
|
if (m_cap)
|
||||||
|
m_cap->clear_message_sender();
|
||||||
|
m_cap.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString PluginPage::web_base_url() const
|
||||||
|
{
|
||||||
|
const auto path = (boost::filesystem::path(resources_dir()) / "web").make_preferred().string();
|
||||||
|
return wxString("file://") + GUI::from_u8(path) + "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString PluginPage::bootstrap_url() const
|
||||||
|
{
|
||||||
|
const auto path = (boost::filesystem::path(resources_dir()) / "web/dialog/PluginWebDialog/blank.html").make_preferred().string();
|
||||||
|
return wxString("file://") + GUI::from_u8(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPage::on_bootstrap_event(wxWebViewEvent& event)
|
||||||
|
{
|
||||||
|
load_plugin_content();
|
||||||
|
event.Skip();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPage::load_plugin_content()
|
||||||
|
{
|
||||||
|
if (m_content_loaded || m_browser == nullptr || m_cap == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_content_loaded = true;
|
||||||
|
try {
|
||||||
|
m_browser->SetPage(wxString::FromUTF8(m_cap->get_ui()), web_base_url());
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Failed to load plugin page '" << m_cap->name() << "': " << error.what();
|
||||||
|
detach_capability();
|
||||||
|
} catch (...) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Failed to load plugin page '" << m_cap->name() << "'";
|
||||||
|
detach_capability();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPage::on_new_window(wxWebViewEvent& event)
|
||||||
|
{
|
||||||
|
const wxString url = event.GetURL();
|
||||||
|
if (!url.empty() && m_browser != nullptr)
|
||||||
|
m_browser->LoadURL(url);
|
||||||
|
event.Veto();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPage::on_script_message(wxWebViewEvent& event)
|
||||||
|
{
|
||||||
|
if (!m_cap)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const wxString payload = event.GetString();
|
||||||
|
nlohmann::json root = nlohmann::json::parse(payload.utf8_string(), nullptr, false);
|
||||||
|
if (root.is_discarded() || root.value("channel", std::string()) != "orca" ||
|
||||||
|
root.value("kind", std::string()) != "message")
|
||||||
|
return;
|
||||||
|
|
||||||
|
const auto data = root.find("data");
|
||||||
|
try {
|
||||||
|
m_cap->on_message(data == root.end()
|
||||||
|
? "null"
|
||||||
|
: data->dump(-1, ' ', false, nlohmann::json::error_handler_t::replace));
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Plugin page message handler failed for '" << m_cap->name() << "': " << error.what();
|
||||||
|
} catch (...) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "Plugin page message handler failed for '" << m_cap->name() << "'";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPage::push_message(const std::string& message)
|
||||||
|
{
|
||||||
|
if (m_browser == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// PagesPluginCapability::post_message() already dumps JSON, so accept it as-is; only a
|
||||||
|
// non-JSON payload needs wrapping as a string literal.
|
||||||
|
const std::string payload = nlohmann::json::accept(message)
|
||||||
|
? message
|
||||||
|
: nlohmann::json(message).dump(-1, ' ', false, nlohmann::json::error_handler_t::replace);
|
||||||
|
|
||||||
|
WebView::RunScript(m_browser, wxString::Format(
|
||||||
|
"(function dispatch(payload, attempts) {\n"
|
||||||
|
" if (typeof window.__orcaDispatch === 'function') { window.__orcaDispatch(payload); return; }\n"
|
||||||
|
" if (attempts < 100) window.setTimeout(function() { dispatch(payload, attempts + 1); }, 25);\n"
|
||||||
|
"})({data: %s}, 0);",
|
||||||
|
wxString::FromUTF8(payload)));
|
||||||
|
}
|
||||||
|
|
||||||
|
PluginPages::~PluginPages()
|
||||||
|
{
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPages::initialize(Notebook* parent)
|
||||||
|
{
|
||||||
|
shutdown();
|
||||||
|
m_parent = parent;
|
||||||
|
if (m_parent == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_visible_page_count = GUI::wxGetApp().app_config->get_plugin_pages_visible_count();
|
||||||
|
|
||||||
|
for (const auto& capability : PluginManager::instance().get_plugin_capabilities("", PluginCapabilityType::Pages)) {
|
||||||
|
if (capability)
|
||||||
|
create_page(capability->identity());
|
||||||
|
}
|
||||||
|
relayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPages::shutdown()
|
||||||
|
{
|
||||||
|
while (!m_pages.empty())
|
||||||
|
remove_page(m_pages.begin()->first);
|
||||||
|
m_parent = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPages::set_visible_page_count(int count)
|
||||||
|
{
|
||||||
|
const int clamped = std::clamp(count, PLUGIN_PAGES_VISIBLE_COUNT_MIN, PLUGIN_PAGES_VISIBLE_COUNT_MAX);
|
||||||
|
if (clamped == m_visible_page_count)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_visible_page_count = clamped;
|
||||||
|
relayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<PagesPluginCapability> PluginPages::get_pages_cap(const PluginCapabilityId& id, bool is_enabled) const
|
||||||
|
{
|
||||||
|
auto capability = PluginManager::instance().get_plugin_capability(id, /*only_enabled=*/false);
|
||||||
|
if (!capability || capability->is_enabled() != is_enabled || capability->type() != PluginCapabilityType::Pages)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return std::dynamic_pointer_cast<PagesPluginCapability>(capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PluginPages::create_page(const PluginCapabilityId& id)
|
||||||
|
{
|
||||||
|
if (m_pages.find(id) != m_pages.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto capability = get_pages_cap(id, true);
|
||||||
|
if (!capability)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::string icon;
|
||||||
|
try {
|
||||||
|
icon = capability->get_icon();
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to get icon for plugin " << id.plugin_key << ": " << error.what();
|
||||||
|
} catch (...) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to get icon for plugin " << id.plugin_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* page = new PluginPage(m_parent, std::move(capability));
|
||||||
|
if (!page->is_valid()) {
|
||||||
|
page->Destroy();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!icon.empty()) {
|
||||||
|
try {
|
||||||
|
boost::filesystem::path icon_path(icon);
|
||||||
|
const std::string extension = icon_path.extension().string();
|
||||||
|
if (extension == ".svg" || extension == ".png")
|
||||||
|
icon_path.replace_extension();
|
||||||
|
|
||||||
|
page->set_icon(create_scaled_bitmap(icon_path.string(), m_parent, 20));
|
||||||
|
} catch (const std::exception& error) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to load icon for plugin " << id.plugin_key << ": " << error.what();
|
||||||
|
} catch (...) {
|
||||||
|
BOOST_LOG_TRIVIAL(error) << __FUNCTION__ << " Failed to load icon for plugin " << id.plugin_key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_pages.emplace(id, page);
|
||||||
|
m_order.push_back(id);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPages::on_cap_register(const PluginCapabilityId& id)
|
||||||
|
{
|
||||||
|
if (m_parent == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (create_page(id))
|
||||||
|
relayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPages::on_cap_deregister(const PluginCapabilityId& id)
|
||||||
|
{
|
||||||
|
remove_page(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPages::on_plugin_register(const std::string& plugin_key)
|
||||||
|
{
|
||||||
|
for (const auto& capability : PluginManager::instance().get_plugin_capabilities(plugin_key, PluginCapabilityType::Pages)) {
|
||||||
|
if (capability)
|
||||||
|
on_cap_register(capability->identity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPages::on_plugin_deregister(const std::string& plugin_key)
|
||||||
|
{
|
||||||
|
for (auto it = m_pages.begin(); it != m_pages.end();) {
|
||||||
|
if (it->first.plugin_key != plugin_key) {
|
||||||
|
++it;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const PluginCapabilityId id = it->first;
|
||||||
|
++it;
|
||||||
|
remove_page(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPages::remove_page(const PluginCapabilityId& id)
|
||||||
|
{
|
||||||
|
auto it = m_pages.find(id);
|
||||||
|
if (it == m_pages.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
PluginPage* page = it->second;
|
||||||
|
page->detach_capability();
|
||||||
|
|
||||||
|
m_pages.erase(it);
|
||||||
|
m_order.erase(std::remove(m_order.begin(), m_order.end(), id), m_order.end());
|
||||||
|
|
||||||
|
const int idx = m_parent != nullptr ? m_parent->FindPage(page) : wxNOT_FOUND;
|
||||||
|
if (idx != wxNOT_FOUND)
|
||||||
|
m_parent->RemovePage(idx);
|
||||||
|
|
||||||
|
relayout();
|
||||||
|
page->Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
wxString PluginPages::page_tab_id(const PluginCapabilityId& id)
|
||||||
|
{
|
||||||
|
return wxString::FromUTF8("plugin." + id.plugin_key + "." + id.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPages::relayout()
|
||||||
|
{
|
||||||
|
if (m_parent == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_order.erase(std::remove_if(m_order.begin(), m_order.end(),
|
||||||
|
[this](const PluginCapabilityId& id) {
|
||||||
|
const bool orphaned = m_pages.find(id) == m_pages.end();
|
||||||
|
if (orphaned)
|
||||||
|
BOOST_LOG_TRIVIAL(error) << "PluginPages::relayout: '" << id.name << "' was in m_order but not m_pages, dropping";
|
||||||
|
return orphaned;
|
||||||
|
}),
|
||||||
|
m_order.end());
|
||||||
|
|
||||||
|
const int visible_slots = std::max(1, m_visible_page_count);
|
||||||
|
const bool need_overflow = static_cast<int>(m_order.size()) > visible_slots;
|
||||||
|
|
||||||
|
// Every visible slot is a normal, individual tab hosting its own page. When there's
|
||||||
|
// overflow, the last slot's page is swappable via m_overflow_button/show_overflow_menu()
|
||||||
|
// rather than being a fixed page — m_swapped_in_id tracks which one currently sits there.
|
||||||
|
std::vector<PluginCapabilityId> tab_ids;
|
||||||
|
if (!need_overflow) {
|
||||||
|
tab_ids = m_order;
|
||||||
|
m_swapped_in_id.reset();
|
||||||
|
} else {
|
||||||
|
const auto overflow_begin = m_order.begin() + (visible_slots - 1);
|
||||||
|
tab_ids.assign(m_order.begin(), overflow_begin);
|
||||||
|
|
||||||
|
if (!m_swapped_in_id || std::find(overflow_begin, m_order.end(), *m_swapped_in_id) == m_order.end())
|
||||||
|
m_swapped_in_id = *overflow_begin;
|
||||||
|
tab_ids.push_back(*m_swapped_in_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// MainFrame::show_device() relayouts on every printer change and most of those change
|
||||||
|
// nothing, so only touch the notebook when the trailing slots don't already spell out
|
||||||
|
// tab_ids — a rebuild destroys and recreates every tab button and rasterizes every icon.
|
||||||
|
const size_t page_count = m_parent->GetPageCount();
|
||||||
|
bool up_to_date = page_count >= tab_ids.size();
|
||||||
|
for (size_t i = 0; up_to_date && i < tab_ids.size(); ++i)
|
||||||
|
up_to_date = m_parent->GetPageName(page_count - tab_ids.size() + i) == page_tab_id(tab_ids[i]);
|
||||||
|
for (const auto& [id, page] : m_pages) {
|
||||||
|
if (!up_to_date)
|
||||||
|
break;
|
||||||
|
const bool wanted = std::find(tab_ids.begin(), tab_ids.end(), id) != tab_ids.end();
|
||||||
|
up_to_date = (m_parent->FindPage(page) != wxNOT_FOUND) == wanted;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!up_to_date) {
|
||||||
|
const wxString id_to_reselect = m_parent->GetSelectedPageName();
|
||||||
|
|
||||||
|
for (const auto& [id, page] : m_pages) {
|
||||||
|
const int idx = m_parent->FindPage(page);
|
||||||
|
if (idx != wxNOT_FOUND)
|
||||||
|
m_parent->RemovePage(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& id : tab_ids) {
|
||||||
|
PluginPage* page = m_pages.at(id);
|
||||||
|
m_parent->InsertPage(m_parent->GetPageCount(), page_tab_id(id), page, wxString::FromUTF8(id.name), "",
|
||||||
|
false, page->icon());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!id_to_reselect.empty())
|
||||||
|
m_parent->SelectPageByName(id_to_reselect);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (need_overflow) {
|
||||||
|
if (m_overflow_button == nullptr) {
|
||||||
|
auto* btn = new Button(m_parent->GetBtnsListCtrl(), wxString(L"\u25BE"), wxString(), wxNO_BORDER);
|
||||||
|
btn->SetCornerRadius(0);
|
||||||
|
const int em = em_unit(m_parent);
|
||||||
|
btn->SetMinSize({40 * em / 10, 36 * em / 10});
|
||||||
|
btn->Bind(wxEVT_BUTTON, [this](wxCommandEvent&) { show_overflow_menu(); });
|
||||||
|
GUI::wxGetApp().UpdateDarkUI(btn);
|
||||||
|
m_overflow_button = btn;
|
||||||
|
}
|
||||||
|
m_parent->SetOverflowButton(m_overflow_button);
|
||||||
|
} else if (m_overflow_button != nullptr) {
|
||||||
|
m_parent->SetOverflowButton(nullptr);
|
||||||
|
m_overflow_button->Destroy();
|
||||||
|
m_overflow_button = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PluginPages::show_overflow_menu()
|
||||||
|
{
|
||||||
|
const int visible_slots = std::max(1, m_visible_page_count);
|
||||||
|
if (m_overflow_button == nullptr || static_cast<int>(m_order.size()) <= visible_slots)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const std::vector<PluginCapabilityId> overflow_ids(m_order.begin() + (visible_slots - 1), m_order.end());
|
||||||
|
|
||||||
|
wxMenu menu;
|
||||||
|
for (size_t i = 0; i < overflow_ids.size(); ++i)
|
||||||
|
menu.AppendRadioItem(static_cast<int>(wxID_HIGHEST + 1 + i), wxString::FromUTF8(overflow_ids[i].name));
|
||||||
|
if (m_swapped_in_id) {
|
||||||
|
const auto it = std::find(overflow_ids.begin(), overflow_ids.end(), *m_swapped_in_id);
|
||||||
|
if (it != overflow_ids.end())
|
||||||
|
menu.Check(static_cast<int>(wxID_HIGHEST + 1 + (it - overflow_ids.begin())), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
menu.Bind(wxEVT_MENU, [this, overflow_ids](wxCommandEvent& evt) {
|
||||||
|
const size_t index = static_cast<size_t>(evt.GetId() - (wxID_HIGHEST + 1));
|
||||||
|
if (index >= overflow_ids.size())
|
||||||
|
return;
|
||||||
|
m_swapped_in_id = overflow_ids[index];
|
||||||
|
relayout();
|
||||||
|
m_parent->SelectPageByName(page_tab_id(*m_swapped_in_id));
|
||||||
|
});
|
||||||
|
m_overflow_button->PopupMenu(&menu);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <slic3r/plugin/PythonPluginInterface.hpp>
|
||||||
|
#include <slic3r/plugin/pluginTypes/pages/PagesPluginCapability.hpp>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <map>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <wx/bitmap.h>
|
||||||
|
#include <wx/panel.h>
|
||||||
|
#include <wx/webview.h>
|
||||||
|
|
||||||
|
class Notebook;
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class PluginPage : public wxPanel
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PluginPage(wxWindow* parent, std::shared_ptr<PagesPluginCapability> capability);
|
||||||
|
~PluginPage() override;
|
||||||
|
|
||||||
|
PluginPage() = delete;
|
||||||
|
|
||||||
|
bool is_valid() const { return m_browser != nullptr && m_cap != nullptr; }
|
||||||
|
void detach_capability();
|
||||||
|
void on_bootstrap_event(wxWebViewEvent& event);
|
||||||
|
void on_new_window(wxWebViewEvent& event);
|
||||||
|
void on_script_message(wxWebViewEvent& event);
|
||||||
|
void push_message(const std::string& message);
|
||||||
|
void set_icon(const wxBitmap& icon) { m_icon = icon; }
|
||||||
|
const wxBitmap& icon() const { return m_icon; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void load_plugin_content();
|
||||||
|
wxString bootstrap_url() const;
|
||||||
|
wxString web_base_url() const;
|
||||||
|
|
||||||
|
wxWebView* m_browser{nullptr};
|
||||||
|
std::shared_ptr<PagesPluginCapability> m_cap;
|
||||||
|
std::shared_ptr<std::atomic<PluginPage*>> m_lifetime;
|
||||||
|
bool m_content_loaded{false};
|
||||||
|
wxBitmap m_icon;
|
||||||
|
};
|
||||||
|
|
||||||
|
class PluginPages
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
PluginPages() = default;
|
||||||
|
~PluginPages();
|
||||||
|
|
||||||
|
PluginPages(const PluginPages&) = delete;
|
||||||
|
PluginPages& operator=(const PluginPages&) = delete;
|
||||||
|
|
||||||
|
void initialize(Notebook* parent);
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
void on_cap_register(const PluginCapabilityId& id);
|
||||||
|
void on_cap_deregister(const PluginCapabilityId& id);
|
||||||
|
void on_plugin_register(const std::string& plugin_key);
|
||||||
|
void on_plugin_deregister(const std::string& plugin_key);
|
||||||
|
|
||||||
|
void set_visible_page_count(int count);
|
||||||
|
|
||||||
|
void relayout();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::shared_ptr<PagesPluginCapability> get_pages_cap(const PluginCapabilityId& id, bool is_enabled) const;
|
||||||
|
bool create_page(const PluginCapabilityId& id);
|
||||||
|
void remove_page(const PluginCapabilityId& id);
|
||||||
|
|
||||||
|
void show_overflow_menu();
|
||||||
|
static wxString page_tab_id(const PluginCapabilityId& id);
|
||||||
|
|
||||||
|
std::map<PluginCapabilityId, PluginPage*> m_pages;
|
||||||
|
std::vector<PluginCapabilityId> m_order;
|
||||||
|
Notebook* m_parent{nullptr};
|
||||||
|
|
||||||
|
int m_visible_page_count{0};
|
||||||
|
|
||||||
|
std::optional<PluginCapabilityId> m_swapped_in_id;
|
||||||
|
wxWindow* m_overflow_button{nullptr};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
#include "PagesPluginCapability.hpp"
|
||||||
|
#include "PagesPluginCapabilityTrampoline.hpp"
|
||||||
|
|
||||||
|
#include "../../PluginFsUtils.hpp"
|
||||||
|
|
||||||
|
#include <boost/log/trivial.hpp>
|
||||||
|
#include <pybind11/pybind11.h>
|
||||||
|
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace py = pybind11;
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
void PagesPluginCapability::RegisterBindings(pybind11::module_& module)
|
||||||
|
{
|
||||||
|
BOOST_LOG_TRIVIAL(debug) << "Registering orca.pages bindings";
|
||||||
|
|
||||||
|
auto pages = module.def_submodule("pages", "Plugin page API");
|
||||||
|
|
||||||
|
py::class_<PagesPluginCapability, PluginCapabilityInterface, PyPagesPluginCapabilityTrampoline,
|
||||||
|
std::shared_ptr<PagesPluginCapability>>(pages, "PagesPluginCapabilityBase")
|
||||||
|
.def(py::init<>())
|
||||||
|
.def("get_type", &PagesPluginCapability::get_type)
|
||||||
|
.def("get_ui", &PagesPluginCapability::get_ui)
|
||||||
|
.def("get_icon", &PagesPluginCapability::get_icon)
|
||||||
|
.def("on_message", &PagesPluginCapability::on_message)
|
||||||
|
.def(
|
||||||
|
"post_message",
|
||||||
|
[](PagesPluginCapability& capability, py::object data) {
|
||||||
|
capability.post_message(py_to_json(data).dump());
|
||||||
|
},
|
||||||
|
py::arg("data"), "Send a JSON-compatible value to the page's window.orca.onMessage handlers.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void PagesPluginCapability::post_message(std::string message)
|
||||||
|
{
|
||||||
|
std::function<void(const std::string&)> sender;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_message_mutex);
|
||||||
|
sender = m_message_sender;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sender)
|
||||||
|
sender(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PagesPluginCapability::set_message_sender(std::function<void(const std::string&)> sender)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_message_mutex);
|
||||||
|
m_message_sender = std::move(sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PagesPluginCapability::clear_message_sender()
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(m_message_mutex);
|
||||||
|
m_message_sender = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
#ifndef slic3r_PagesPluginCapability_hpp_
|
||||||
|
#define slic3r_PagesPluginCapability_hpp_
|
||||||
|
|
||||||
|
#include "../../PythonPluginInterface.hpp"
|
||||||
|
#include "pybind11/pybind11.h"
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
class PagesPluginCapability : public PluginCapabilityInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static void RegisterBindings(pybind11::module_& module);
|
||||||
|
|
||||||
|
PluginCapabilityType get_type() const override { return PluginCapabilityType::Pages; }
|
||||||
|
|
||||||
|
virtual std::string get_ui() = 0;
|
||||||
|
virtual void on_message(std::string message) { (void) message; }
|
||||||
|
virtual std::string get_icon() { return {}; }
|
||||||
|
|
||||||
|
void post_message(std::string message);
|
||||||
|
void set_message_sender(std::function<void(const std::string&)> sender);
|
||||||
|
void clear_message_sender();
|
||||||
|
|
||||||
|
private:
|
||||||
|
mutable std::mutex m_message_mutex;
|
||||||
|
std::function<void(const std::string&)> m_message_sender;
|
||||||
|
};
|
||||||
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "PagesPluginCapability.hpp"
|
||||||
|
#include "../../PluginFsUtils.hpp"
|
||||||
|
#include "../../PyPluginTrampoline.hpp"
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
namespace Slic3r {
|
||||||
|
|
||||||
|
class PyPagesPluginCapabilityTrampoline : public PyPluginCommonTrampoline<PagesPluginCapability>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using PyPluginCommonTrampoline<PagesPluginCapability>::PyPluginCommonTrampoline;
|
||||||
|
|
||||||
|
std::string get_icon() override
|
||||||
|
{
|
||||||
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
|
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
||||||
|
[] {},
|
||||||
|
PYBIND11_OVERRIDE,
|
||||||
|
std::string,
|
||||||
|
PagesPluginCapability,
|
||||||
|
get_icon);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string get_ui() override
|
||||||
|
{
|
||||||
|
ORCA_PY_OVERRIDE_AUDITED(
|
||||||
|
::Slic3r::PluginAuditManager::AuditMode::Loading,
|
||||||
|
[] {},
|
||||||
|
PYBIND11_OVERRIDE_PURE,
|
||||||
|
std::string,
|
||||||
|
PagesPluginCapability,
|
||||||
|
get_ui);
|
||||||
|
}
|
||||||
|
|
||||||
|
void on_message(std::string message) override
|
||||||
|
{
|
||||||
|
PluginCapabilityInterface::RefCounter ref_counter(*this);
|
||||||
|
PythonGILState gil;
|
||||||
|
if (!gil)
|
||||||
|
throw std::runtime_error("Python interpreter is shutting down");
|
||||||
|
|
||||||
|
ORCA_PY_AUDIT_SCOPE(::Slic3r::PluginAuditManager::AuditMode::Loading);
|
||||||
|
|
||||||
|
pybind11::function override = pybind11::get_override(static_cast<PagesPluginCapability*>(this), "on_message");
|
||||||
|
if (!override)
|
||||||
|
return;
|
||||||
|
|
||||||
|
nlohmann::json data = nlohmann::json::parse(message, nullptr, false);
|
||||||
|
if (data.is_discarded())
|
||||||
|
data = message;
|
||||||
|
|
||||||
|
ORCA_PY_LOGGED_OVERRIDE_BODY(override(::Slic3r::json_to_py(data)));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Slic3r
|
||||||
@@ -13,10 +13,8 @@ namespace py = pybind11;
|
|||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
|
|
||||||
void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module, pybind11::enum_<PluginCapabilityType>& pluginTypes)
|
void PrinterAgentPluginCapability::RegisterBindings(pybind11::module_& module)
|
||||||
{
|
{
|
||||||
(void) pluginTypes;
|
|
||||||
|
|
||||||
auto printer_agent_module = module.def_submodule("printer_agent", "Printer Agent API");
|
auto printer_agent_module = module.def_submodule("printer_agent", "Printer Agent API");
|
||||||
|
|
||||||
py::enum_<FilamentSyncMode>(printer_agent_module, "FilamentSyncMode")
|
py::enum_<FilamentSyncMode>(printer_agent_module, "FilamentSyncMode")
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ namespace Slic3r {
|
|||||||
class PrinterAgentPluginCapability : public PluginCapabilityInterface, public IPrinterAgent
|
class PrinterAgentPluginCapability : public PluginCapabilityInterface, public IPrinterAgent
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
static void RegisterBindings(pybind11::module_& module, pybind11::enum_<PluginCapabilityType>& pluginTypes);
|
static void RegisterBindings(pybind11::module_& module);
|
||||||
|
|
||||||
PluginCapabilityType get_type() const override { return PluginCapabilityType::PrinterConnection; }
|
PluginCapabilityType get_type() const override { return PluginCapabilityType::PrinterConnection; }
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,8 @@
|
|||||||
namespace py = pybind11;
|
namespace py = pybind11;
|
||||||
|
|
||||||
namespace Slic3r {
|
namespace Slic3r {
|
||||||
void ScriptPluginCapability::RegisterBindings(pybind11::module_& module, pybind11::enum_<PluginCapabilityType>& pluginTypes)
|
void ScriptPluginCapability::RegisterBindings(pybind11::module_& module)
|
||||||
{
|
{
|
||||||
(void) pluginTypes;
|
|
||||||
BOOST_LOG_TRIVIAL(debug) << "Registering orca.script bindings";
|
BOOST_LOG_TRIVIAL(debug) << "Registering orca.script bindings";
|
||||||
|
|
||||||
auto script = module.def_submodule("script", "Script Plugins API");
|
auto script = module.def_submodule("script", "Script Plugins API");
|
||||||
|
|||||||
@@ -11,8 +11,7 @@ public:
|
|||||||
|
|
||||||
virtual ExecutionResult execute() = 0;
|
virtual ExecutionResult execute() = 0;
|
||||||
|
|
||||||
static void RegisterBindings(pybind11::module_ &module,
|
static void RegisterBindings(pybind11::module_ &module);
|
||||||
pybind11::enum_<PluginCapabilityType> &pluginTypes);
|
|
||||||
};
|
};
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ namespace Slic3r {
|
|||||||
|
|
||||||
bool SlicingPipelineContext::cancelled() const { return print && print->canceled(); }
|
bool SlicingPipelineContext::cancelled() const { return print && print->canceled(); }
|
||||||
|
|
||||||
void SlicingPipelinePluginCapability::RegisterBindings(py::module_& module, py::enum_<PluginCapabilityType>& pluginTypes) {
|
void SlicingPipelinePluginCapability::RegisterBindings(py::module_& module) {
|
||||||
(void) pluginTypes; // unused: this capability defines its own Step enum (below) rather than extending the shared PluginCapabilityType enum.
|
|
||||||
auto slicing = module.def_submodule("slicing", "Slicing pipeline API (research/experimental).");
|
auto slicing = module.def_submodule("slicing", "Slicing pipeline API (research/experimental).");
|
||||||
|
|
||||||
py::enum_<SlicingPipelineStepPlugin>(slicing, "Step")
|
py::enum_<SlicingPipelineStepPlugin>(slicing, "Step")
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ public:
|
|||||||
// Runs on the slicing worker thread. Do not call orca.host.ui.* here: the UI thread can be
|
// Runs on the slicing worker thread. Do not call orca.host.ui.* here: the UI thread can be
|
||||||
// blocked waiting on the slicing worker, so a marshaled UI call from this thread can deadlock.
|
// blocked waiting on the slicing worker, so a marshaled UI call from this thread can deadlock.
|
||||||
virtual ExecutionResult execute(SlicingPipelineContext& ctx) = 0;
|
virtual ExecutionResult execute(SlicingPipelineContext& ctx) = 0;
|
||||||
static void RegisterBindings(pybind11::module_& module, pybind11::enum_<PluginCapabilityType>& pluginTypes);
|
static void RegisterBindings(pybind11::module_& module);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Slic3r
|
} // namespace Slic3r
|
||||||
|
|||||||
@@ -698,3 +698,290 @@ TEST_CASE("Solid infill direction offsets every layer when no template is set",
|
|||||||
CHECK(delta == 30);
|
CHECK(delta == 30);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Honeycomb infill rounds its cell corners with the smooth factor", "[Fill]")
|
||||||
|
{
|
||||||
|
// A cell whose sides are several times the line width, so that the corners have room to be rounded.
|
||||||
|
const double spacing = 0.45;
|
||||||
|
const double density = 0.1;
|
||||||
|
auto fill = [spacing, density](double smooth_factor) {
|
||||||
|
std::unique_ptr<Slic3r::Fill> filler(Slic3r::Fill::new_from_type("honeycomb"));
|
||||||
|
filler->spacing = spacing;
|
||||||
|
|
||||||
|
FillParams params;
|
||||||
|
params.density = float(density);
|
||||||
|
params.dont_adjust = true;
|
||||||
|
// Keep the fragments apart, so that only the turns of the pattern itself are measured.
|
||||||
|
params.anchor_length_max = 0.f;
|
||||||
|
params.smooth_factor = smooth_factor;
|
||||||
|
|
||||||
|
Slic3r::ExPolygon square{ Slic3r::Points{
|
||||||
|
Point::new_scale(0., 0.), Point::new_scale(50., 0.), Point::new_scale(50., 50.), Point::new_scale(0., 50.) } };
|
||||||
|
Slic3r::Surface surface(stInternal, square);
|
||||||
|
return filler->fill_surface(&surface, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cosine of the sharpest turn of any of the paths, 1 meaning none of them turns at all.
|
||||||
|
auto sharpest_turn_cosine = [](const Slic3r::Polylines &polylines) {
|
||||||
|
double sharpest = 1.;
|
||||||
|
for (const Polyline &polyline : polylines)
|
||||||
|
for (size_t i = 1; i + 1 < polyline.size(); ++i) {
|
||||||
|
const Vec2d incoming = (polyline[i] - polyline[i - 1]).cast<double>().normalized();
|
||||||
|
const Vec2d outgoing = (polyline[i + 1] - polyline[i]).cast<double>().normalized();
|
||||||
|
sharpest = std::min(sharpest, incoming.dot(outgoing));
|
||||||
|
}
|
||||||
|
return sharpest;
|
||||||
|
};
|
||||||
|
auto point_count = [](const Slic3r::Polylines &polylines) {
|
||||||
|
return std::accumulate(polylines.begin(), polylines.end(), size_t(0),
|
||||||
|
[](size_t count, const Polyline &polyline) { return count + polyline.size(); });
|
||||||
|
};
|
||||||
|
|
||||||
|
const Slic3r::Polylines sharp = fill(0.);
|
||||||
|
const Slic3r::Polylines smooth = fill(1.);
|
||||||
|
|
||||||
|
REQUIRE(!sharp.empty());
|
||||||
|
REQUIRE(smooth.size() == sharp.size());
|
||||||
|
REQUIRE(point_count(smooth) > point_count(sharp));
|
||||||
|
// The cell corners turn by 60 degrees; smoothing replaces them by gentle curves.
|
||||||
|
REQUIRE(sharpest_turn_cosine(sharp) < 0.6);
|
||||||
|
REQUIRE(sharpest_turn_cosine(smooth) > 0.9);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Point count, number of turns sharper than 25 degrees and length of the sparse infill of a print.
|
||||||
|
// A rounded corner is a run of much gentler turns, so smoothing shows up as fewer sharp ones.
|
||||||
|
struct SparseInfillShape {
|
||||||
|
size_t point_count { 0 };
|
||||||
|
size_t sharp_turns { 0 };
|
||||||
|
size_t path_count { 0 };
|
||||||
|
double length { 0. };
|
||||||
|
};
|
||||||
|
|
||||||
|
static SparseInfillShape sparse_infill_shape(const Print &print)
|
||||||
|
{
|
||||||
|
SparseInfillShape shape;
|
||||||
|
|
||||||
|
auto account = [&shape](const ExtrusionPath &path) {
|
||||||
|
if (!sparse_role(path.role()))
|
||||||
|
return;
|
||||||
|
const Points3 &pts = path.polyline.points;
|
||||||
|
++shape.path_count;
|
||||||
|
shape.point_count += pts.size();
|
||||||
|
for (size_t i = 1; i < pts.size(); ++i)
|
||||||
|
shape.length += (pts[i] - pts[i - 1]).head<2>().cast<double>().norm();
|
||||||
|
for (size_t i = 1; i + 1 < pts.size(); ++i) {
|
||||||
|
const Vec2d incoming = (pts[i] - pts[i - 1]).head<2>().cast<double>();
|
||||||
|
const Vec2d outgoing = (pts[i + 1] - pts[i]).head<2>().cast<double>();
|
||||||
|
if (incoming.squaredNorm() > 0. && outgoing.squaredNorm() > 0. &&
|
||||||
|
incoming.normalized().dot(outgoing.normalized()) < 0.9)
|
||||||
|
++shape.sharp_turns;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const Layer *layer : print.objects().front()->layers())
|
||||||
|
for (const LayerRegion *region : layer->regions())
|
||||||
|
for (const ExtrusionEntity *entity : region->fills.flatten().entities) {
|
||||||
|
if (auto *path = dynamic_cast<const ExtrusionPath *>(entity))
|
||||||
|
account(*path);
|
||||||
|
else if (auto *multi = dynamic_cast<const ExtrusionMultiPath *>(entity))
|
||||||
|
for (const ExtrusionPath &p : multi->paths)
|
||||||
|
account(p);
|
||||||
|
else if (auto *loop = dynamic_cast<const ExtrusionLoop *>(entity))
|
||||||
|
for (const ExtrusionPath &p : loop->paths)
|
||||||
|
account(p);
|
||||||
|
}
|
||||||
|
return shape;
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Lightning infill rounds the turns of its branches with the smooth factor", "[Fill]")
|
||||||
|
{
|
||||||
|
auto shape_for = [](const std::string &smooth_factor) {
|
||||||
|
Print print;
|
||||||
|
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print,
|
||||||
|
{{"sparse_infill_pattern", "lightning"},
|
||||||
|
{"sparse_infill_density", "15%"},
|
||||||
|
{"sparse_infill_smooth_factor", smooth_factor},
|
||||||
|
{"layer_height", 0.2}});
|
||||||
|
return sparse_infill_shape(print);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SparseInfillShape sharp = shape_for("0%");
|
||||||
|
const SparseInfillShape smooth = shape_for("100%");
|
||||||
|
|
||||||
|
REQUIRE(sharp.point_count > 0);
|
||||||
|
// The branch turns are replaced by curves, which cut the corners off and take more points to
|
||||||
|
// describe. The turns where two branches are joined into one path stay sharp.
|
||||||
|
REQUIRE(smooth.point_count > sharp.point_count);
|
||||||
|
REQUIRE(smooth.sharp_turns < sharp.sharp_turns);
|
||||||
|
REQUIRE(smooth.length < sharp.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Concentric infill rounds its loops with the smooth factor", "[Fill]")
|
||||||
|
{
|
||||||
|
auto shape_for = [](const std::string &smooth_factor) {
|
||||||
|
Print print;
|
||||||
|
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print,
|
||||||
|
{{"sparse_infill_pattern", "concentric"},
|
||||||
|
{"sparse_infill_density", "20%"},
|
||||||
|
{"sparse_infill_smooth_factor", smooth_factor},
|
||||||
|
{"layer_height", 0.2}});
|
||||||
|
return sparse_infill_shape(print);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SparseInfillShape sharp = shape_for("0%");
|
||||||
|
const SparseInfillShape smooth = shape_for("100%");
|
||||||
|
|
||||||
|
REQUIRE(sharp.point_count > 0);
|
||||||
|
REQUIRE(smooth.point_count > sharp.point_count);
|
||||||
|
REQUIRE(smooth.sharp_turns < sharp.sharp_turns);
|
||||||
|
REQUIRE(smooth.length < sharp.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Cross hatch infill rounds its transition layers with the smooth factor", "[Fill]")
|
||||||
|
{
|
||||||
|
auto shape_for = [](const std::string &smooth_factor) {
|
||||||
|
Print print;
|
||||||
|
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print,
|
||||||
|
{{"sparse_infill_pattern", "crosshatch"},
|
||||||
|
{"sparse_infill_density", "20%"},
|
||||||
|
{"sparse_infill_smooth_factor", smooth_factor},
|
||||||
|
{"layer_height", 0.2}});
|
||||||
|
return sparse_infill_shape(print);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SparseInfillShape sharp = shape_for("0%");
|
||||||
|
const SparseInfillShape smooth = shape_for("100%");
|
||||||
|
|
||||||
|
REQUIRE(sharp.point_count > 0);
|
||||||
|
REQUIRE(smooth.point_count > sharp.point_count);
|
||||||
|
REQUIRE(smooth.sharp_turns < sharp.sharp_turns);
|
||||||
|
REQUIRE(smooth.length < sharp.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Trapezoidal grid infill rounds its corners only with more than one line", "[Fill]")
|
||||||
|
{
|
||||||
|
auto shape_for = [](int multiline, const std::string &smooth_factor) {
|
||||||
|
Print print;
|
||||||
|
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print,
|
||||||
|
{{"sparse_infill_pattern", "grid"},
|
||||||
|
{"sparse_infill_density", "20%"},
|
||||||
|
{"fill_multiline", multiline},
|
||||||
|
{"sparse_infill_smooth_factor", smooth_factor},
|
||||||
|
{"layer_height", 0.2}});
|
||||||
|
return sparse_infill_shape(print);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SparseInfillShape sharp = shape_for(2, "0%");
|
||||||
|
const SparseInfillShape smooth = shape_for(2, "100%");
|
||||||
|
|
||||||
|
REQUIRE(sharp.point_count > 0);
|
||||||
|
REQUIRE(smooth.point_count > sharp.point_count);
|
||||||
|
REQUIRE(smooth.sharp_turns < sharp.sharp_turns);
|
||||||
|
REQUIRE(smooth.length < sharp.length);
|
||||||
|
|
||||||
|
// A single line per infill wall is the plain crossing line grid, which has no corner of its own.
|
||||||
|
const SparseInfillShape single_sharp = shape_for(1, "0%");
|
||||||
|
const SparseInfillShape single_smooth = shape_for(1, "100%");
|
||||||
|
REQUIRE(single_sharp.point_count > 0);
|
||||||
|
REQUIRE(single_smooth.point_count == single_sharp.point_count);
|
||||||
|
REQUIRE(single_smooth.length == single_sharp.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("3D honeycomb infill rounds its octahedral waves with the smooth factor", "[Fill]")
|
||||||
|
{
|
||||||
|
auto shape_for = [](const std::string &smooth_factor) {
|
||||||
|
Print print;
|
||||||
|
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print,
|
||||||
|
{{"sparse_infill_pattern", "3dhoneycomb"},
|
||||||
|
{"sparse_infill_density", "20%"},
|
||||||
|
{"sparse_infill_smooth_factor", smooth_factor},
|
||||||
|
{"layer_height", 0.2}});
|
||||||
|
return sparse_infill_shape(print);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SparseInfillShape sharp = shape_for("0%");
|
||||||
|
const SparseInfillShape smooth = shape_for("100%");
|
||||||
|
|
||||||
|
REQUIRE(sharp.point_count > 0);
|
||||||
|
REQUIRE(smooth.point_count > sharp.point_count);
|
||||||
|
REQUIRE(smooth.sharp_turns < sharp.sharp_turns);
|
||||||
|
REQUIRE(smooth.length < sharp.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Smoothed concentric infill stays inside the fill region", "[Fill][Regression]")
|
||||||
|
{
|
||||||
|
// The concentric loops are offsets of the fill region and are never clipped to it, so a corner
|
||||||
|
// rounded across its boundary ends up in a hole or over a wall. Rounding cuts toward the inside of
|
||||||
|
// the turn, which leaves the region at every corner of a hole, and in a region thinner than the
|
||||||
|
// curve even at a corner turning inwards.
|
||||||
|
const bool thin_region = GENERATE(false, true);
|
||||||
|
ExPolygon region;
|
||||||
|
if (thin_region) {
|
||||||
|
// An L of two 1.2mm wide arms: cutting the corner they meet at crosses both of them.
|
||||||
|
region = ExPolygon{ Slic3r::Points{
|
||||||
|
Point::new_scale(0., 0.), Point::new_scale(20., 0.), Point::new_scale(20., 1.2),
|
||||||
|
Point::new_scale(1.2, 1.2), Point::new_scale(1.2, 20.), Point::new_scale(0., 20.) } };
|
||||||
|
} else {
|
||||||
|
region = ExPolygon{ Slic3r::Points{ Point::new_scale(0., 0.), Point::new_scale(50., 0.),
|
||||||
|
Point::new_scale(50., 50.), Point::new_scale(0., 50.) },
|
||||||
|
Slic3r::Points{ Point::new_scale(30., 20.), Point::new_scale(30., 30.),
|
||||||
|
Point::new_scale(20., 30.), Point::new_scale(20., 20.) } };
|
||||||
|
}
|
||||||
|
CAPTURE(thin_region);
|
||||||
|
|
||||||
|
auto fill = [®ion](double smooth_factor) {
|
||||||
|
std::unique_ptr<Slic3r::Fill> filler(Slic3r::Fill::new_from_type("concentric"));
|
||||||
|
filler->spacing = 0.45;
|
||||||
|
|
||||||
|
FillParams params;
|
||||||
|
params.density = 0.1f;
|
||||||
|
params.dont_adjust = true;
|
||||||
|
params.smooth_factor = smooth_factor;
|
||||||
|
|
||||||
|
Slic3r::Surface surface(stInternal, region);
|
||||||
|
return filler->fill_surface(&surface, params);
|
||||||
|
};
|
||||||
|
auto point_count = [](const Slic3r::Polylines &polylines) {
|
||||||
|
return std::accumulate(polylines.begin(), polylines.end(), size_t(0),
|
||||||
|
[](size_t count, const Polyline &polyline) { return count + polyline.size(); });
|
||||||
|
};
|
||||||
|
|
||||||
|
const Slic3r::Polylines sharp = fill(0.);
|
||||||
|
const Slic3r::Polylines smooth = fill(1.);
|
||||||
|
REQUIRE(!sharp.empty());
|
||||||
|
|
||||||
|
// Nothing leaves the fill region, which the unrounded loops already touch from the inside.
|
||||||
|
const ExPolygons bounds = offset_ex(region, float(SCALED_EPSILON));
|
||||||
|
REQUIRE(diff_pl(sharp, bounds).empty());
|
||||||
|
REQUIRE(diff_pl(smooth, bounds).empty());
|
||||||
|
// The corners that the region has room for are still rounded.
|
||||||
|
if (!thin_region)
|
||||||
|
REQUIRE(point_count(smooth) > point_count(sharp));
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Smoothing multiline lightning infill keeps its outlines connected", "[Fill][Regression]")
|
||||||
|
{
|
||||||
|
// With more than one line per infill wall, the branches are printed as outlines drawn around them,
|
||||||
|
// and the outlines of branches that run close to each other merge into one. Rounding the branches
|
||||||
|
// before those outlines are built moves them apart, which breaks the merged outlines up into
|
||||||
|
// separate loops - many more of them, each needing its own travel move.
|
||||||
|
auto shape_for = [](const std::string &smooth_factor) {
|
||||||
|
Print print;
|
||||||
|
Slic3r::Test::init_and_process_print({Slic3r::Test::cube(20)}, print,
|
||||||
|
{{"sparse_infill_pattern", "lightning"},
|
||||||
|
{"sparse_infill_density", "50%"},
|
||||||
|
{"fill_multiline", 2},
|
||||||
|
{"sparse_infill_smooth_factor", smooth_factor},
|
||||||
|
{"layer_height", 0.2}});
|
||||||
|
return sparse_infill_shape(print);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SparseInfillShape sharp = shape_for("0%");
|
||||||
|
const SparseInfillShape smooth = shape_for("100%");
|
||||||
|
|
||||||
|
REQUIRE(sharp.path_count > 0);
|
||||||
|
REQUIRE(smooth.path_count <= sharp.path_count);
|
||||||
|
// The outlines are still rounded.
|
||||||
|
REQUIRE(smooth.point_count > sharp.point_count);
|
||||||
|
REQUIRE(smooth.sharp_turns < sharp.sharp_turns);
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ add_executable(${_TEST_NAME}_tests
|
|||||||
test_preset_setting_id.cpp
|
test_preset_setting_id.cpp
|
||||||
test_preset_diff.cpp
|
test_preset_diff.cpp
|
||||||
test_elephant_foot_compensation.cpp
|
test_elephant_foot_compensation.cpp
|
||||||
|
test_fill_corner_smoothing.cpp
|
||||||
test_fill_plane_path.cpp
|
test_fill_plane_path.cpp
|
||||||
test_geometry.cpp
|
test_geometry.cpp
|
||||||
test_multimaterial_segmentation.cpp
|
test_multimaterial_segmentation.cpp
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
#include <catch2/catch_all.hpp>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
#include "libslic3r/Fill/FillCornerSmoothing.hpp"
|
||||||
|
#include "libslic3r/Polyline.hpp"
|
||||||
|
#include "libslic3r/libslic3r.h"
|
||||||
|
|
||||||
|
using namespace Slic3r;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
// A right angle turn, with the outgoing leg ten times longer than the incoming one.
|
||||||
|
Polyline asymmetric_corner()
|
||||||
|
{
|
||||||
|
return Polyline{ Point::new_scale(0., 0.), Point::new_scale(10., 0.), Point::new_scale(10., 100.) };
|
||||||
|
}
|
||||||
|
|
||||||
|
double max_turn_cosine(const Polyline &polyline)
|
||||||
|
{
|
||||||
|
double sharpest = 1.;
|
||||||
|
for (size_t i = 1; i + 1 < polyline.size(); ++i) {
|
||||||
|
const Vec2d incoming = (polyline[i] - polyline[i - 1]).cast<double>().normalized();
|
||||||
|
const Vec2d outgoing = (polyline[i + 1] - polyline[i]).cast<double>().normalized();
|
||||||
|
sharpest = std::min(sharpest, incoming.dot(outgoing));
|
||||||
|
}
|
||||||
|
return sharpest;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool contains(const Polyline &polyline, const Point &point)
|
||||||
|
{
|
||||||
|
return std::find(polyline.points.begin(), polyline.points.end(), point) != polyline.points.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
const double tolerance = scaled<double>(0.0125);
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
TEST_CASE("Corner smoothing replaces a sharp vertex by a curve", "[FillCornerSmoothing]")
|
||||||
|
{
|
||||||
|
const Polyline sharp = asymmetric_corner();
|
||||||
|
Polyline smooth = sharp;
|
||||||
|
smooth_polyline_corners(smooth, 1., tolerance);
|
||||||
|
|
||||||
|
REQUIRE(smooth.size() > sharp.size());
|
||||||
|
REQUIRE(smooth.front() == sharp.front());
|
||||||
|
REQUIRE(smooth.back() == sharp.back());
|
||||||
|
// The right angle is gone, every remaining turn is a gentle one.
|
||||||
|
REQUIRE(max_turn_cosine(sharp) < 0.1);
|
||||||
|
REQUIRE(max_turn_cosine(smooth) > 0.9);
|
||||||
|
REQUIRE(smooth.length() < sharp.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Corner smoothing keeps the path untouched at a zero factor", "[FillCornerSmoothing]")
|
||||||
|
{
|
||||||
|
const Polyline sharp = asymmetric_corner();
|
||||||
|
|
||||||
|
Polyline none = sharp;
|
||||||
|
smooth_polyline_corners(none, 0., tolerance);
|
||||||
|
REQUIRE(none.points == sharp.points);
|
||||||
|
|
||||||
|
Polyline invalid = sharp;
|
||||||
|
smooth_polyline_corners(invalid, std::numeric_limits<double>::quiet_NaN(), tolerance);
|
||||||
|
REQUIRE(invalid.points == sharp.points);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Corner smoothing consumes at most half of the shorter leg", "[FillCornerSmoothing]")
|
||||||
|
{
|
||||||
|
// The curve must not reach beyond the middle of either adjoining segment, otherwise the curves of
|
||||||
|
// two adjacent corners would overlap. The shorter leg is 10mm long, so the corner at (10, 0) is
|
||||||
|
// left 5mm before it and rejoined 5mm past it, even though the other leg is 100mm long.
|
||||||
|
Polyline smooth = asymmetric_corner();
|
||||||
|
smooth_polyline_corners(smooth, 1., tolerance);
|
||||||
|
|
||||||
|
REQUIRE(contains(smooth, Point::new_scale(5., 0.)));
|
||||||
|
REQUIRE(contains(smooth, Point::new_scale(10., 5.)));
|
||||||
|
// A Bezier curve stays within the convex hull of its control points, so the rounded path stays
|
||||||
|
// inside the box spanned by the two legs.
|
||||||
|
for (const Point &point : smooth.points) {
|
||||||
|
REQUIRE(point.x() >= 0);
|
||||||
|
REQUIRE(point.y() >= 0);
|
||||||
|
REQUIRE(point.x() <= Point::new_scale(10., 0.).x());
|
||||||
|
REQUIRE(point.y() <= Point::new_scale(0., 100.).y());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Corner smoothing scales the curve with the factor", "[FillCornerSmoothing]")
|
||||||
|
{
|
||||||
|
Polyline half = asymmetric_corner();
|
||||||
|
smooth_polyline_corners(half, 0.5, tolerance);
|
||||||
|
Polyline full = asymmetric_corner();
|
||||||
|
smooth_polyline_corners(full, 1., tolerance);
|
||||||
|
|
||||||
|
// Half of the factor leaves the 10mm leg half as far from the corner.
|
||||||
|
REQUIRE(contains(half, Point::new_scale(7.5, 0.)));
|
||||||
|
REQUIRE(contains(full, Point::new_scale(5., 0.)));
|
||||||
|
// A larger factor rounds a wider portion of the legs, cutting more of the corner off.
|
||||||
|
REQUIRE(full.length() < half.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Corner smoothing leaves hairpins sharp", "[FillCornerSmoothing]")
|
||||||
|
{
|
||||||
|
// Both ends of a curve replacing a nearly reversing turn coincide, which would round the hairpin
|
||||||
|
// into a degenerate loop instead of a tip.
|
||||||
|
Polyline hairpin{ Point::new_scale(0., 0.), Point::new_scale(10., 0.), Point::new_scale(0., 0.5) };
|
||||||
|
const Polyline sharp = hairpin;
|
||||||
|
smooth_polyline_corners(hairpin, 1., tolerance);
|
||||||
|
REQUIRE(hairpin == sharp);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Corner smoothing follows the flattening tolerance", "[FillCornerSmoothing]")
|
||||||
|
{
|
||||||
|
Polyline coarse = asymmetric_corner();
|
||||||
|
smooth_polyline_corners(coarse, 1., scaled<double>(0.2));
|
||||||
|
Polyline fine = asymmetric_corner();
|
||||||
|
smooth_polyline_corners(fine, 1., scaled<double>(0.001));
|
||||||
|
|
||||||
|
REQUIRE(fine.size() > coarse.size());
|
||||||
|
REQUIRE(fine.front() == coarse.front());
|
||||||
|
REQUIRE(fine.back() == coarse.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Corner smoothing emits no zero length segments", "[FillCornerSmoothing]")
|
||||||
|
{
|
||||||
|
// Fully smoothed adjacent corners meet at the midpoint of the segment they share.
|
||||||
|
Polyline zigzag;
|
||||||
|
for (int i = 0; i < 8; ++i)
|
||||||
|
zigzag.points.emplace_back(Point::new_scale(i, i % 2 ? 1. : 0.));
|
||||||
|
smooth_polyline_corners(zigzag, 1., tolerance);
|
||||||
|
|
||||||
|
for (size_t i = 1; i < zigzag.size(); ++i)
|
||||||
|
REQUIRE((zigzag[i] - zigzag[i - 1]).cast<double>().squaredNorm() > 0.);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Corner smoothing rounds every vertex of a polygon", "[FillCornerSmoothing]")
|
||||||
|
{
|
||||||
|
// A polygon closes implicitly, so none of its corners may stay sharp, not even the first one.
|
||||||
|
const Polygon square{ Point::new_scale(0., 0.), Point::new_scale(10., 0.), Point::new_scale(10., 10.),
|
||||||
|
Point::new_scale(0., 10.) };
|
||||||
|
Polygons smooth{ square };
|
||||||
|
smooth_polygons_corners(smooth, 1., tolerance);
|
||||||
|
const Polyline rounded = smooth.front().split_at_first_point();
|
||||||
|
|
||||||
|
REQUIRE(smooth.front().size() > square.size());
|
||||||
|
REQUIRE(max_turn_cosine(rounded) > 0.9);
|
||||||
|
// The turn from the closing segment back into the first one must be gentle as well.
|
||||||
|
const Vec2d incoming = (rounded[rounded.size() - 1] - rounded[rounded.size() - 2]).cast<double>().normalized();
|
||||||
|
const Vec2d outgoing = (rounded[1] - rounded[0]).cast<double>().normalized();
|
||||||
|
REQUIRE(incoming.dot(outgoing) > 0.9);
|
||||||
|
// None of the corners is cut by more than half of a 10mm side.
|
||||||
|
for (const Point &point : smooth.front().points) {
|
||||||
|
REQUIRE(point.x() >= 0);
|
||||||
|
REQUIRE(point.y() >= 0);
|
||||||
|
REQUIRE(point.x() <= Point::new_scale(10., 0.).x());
|
||||||
|
REQUIRE(point.y() <= Point::new_scale(0., 10.).y());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Corner smoothing keeps the ends of a path that returns to its start", "[FillCornerSmoothing][Regression]")
|
||||||
|
{
|
||||||
|
// A branch of a lightning tree walks out and retraces its way back, ending where it started. Its
|
||||||
|
// ends are two free ends that happen to coincide, and joining them would close it into a loop.
|
||||||
|
Polyline retrace{ Point::new_scale(0., 0.), Point::new_scale(10., 0.), Point::new_scale(10., 10.),
|
||||||
|
Point::new_scale(5., 10.), Point::new_scale(0., 0.) };
|
||||||
|
const Polyline sharp = retrace;
|
||||||
|
smooth_polyline_corners(retrace, 1., tolerance);
|
||||||
|
|
||||||
|
REQUIRE(retrace.size() > sharp.size());
|
||||||
|
REQUIRE(retrace.front() == sharp.front());
|
||||||
|
REQUIRE(retrace.back() == sharp.back());
|
||||||
|
}
|
||||||
@@ -27,6 +27,31 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class TestableOctagramSpiral : public FillOctagramSpiral
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Points generate_points(double resolution, double smooth_factor = 0., coord_t max_coordinate = 7)
|
||||||
|
{
|
||||||
|
InfillPolylineOutput output(output_scale);
|
||||||
|
FillParams params;
|
||||||
|
params.smooth_factor = smooth_factor;
|
||||||
|
FillOctagramSpiral::generate(-max_coordinate, -max_coordinate, max_coordinate, max_coordinate, resolution, params, output);
|
||||||
|
return std::move(output.result());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cosine of the sharpest turn of a path, 1 meaning it has no turn at all.
|
||||||
|
double sharpest_turn_cosine(const Points &points)
|
||||||
|
{
|
||||||
|
double sharpest = 1.;
|
||||||
|
for (size_t i = 1; i + 1 < points.size(); ++i) {
|
||||||
|
const Vec2d incoming = (points[i] - points[i - 1]).cast<double>().normalized();
|
||||||
|
const Vec2d outgoing = (points[i + 1] - points[i]).cast<double>().normalized();
|
||||||
|
sharpest = std::min(sharpest, incoming.dot(outgoing));
|
||||||
|
}
|
||||||
|
return sharpest;
|
||||||
|
}
|
||||||
|
|
||||||
double path_length(const Points &points)
|
double path_length(const Points &points)
|
||||||
{
|
{
|
||||||
double length = 0.;
|
double length = 0.;
|
||||||
@@ -146,6 +171,35 @@ TEST_CASE("Hilbert smoothing joins straight segments with continuous curvature",
|
|||||||
REQUIRE(fine_entry_curvature < 0.25 * coarse_entry_curvature);
|
REQUIRE(fine_entry_curvature < 0.25 * coarse_entry_curvature);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Octagram spiral smoothing rounds the turns of the spiral", "[FillPlanePath]")
|
||||||
|
{
|
||||||
|
const Points sharp = TestableOctagramSpiral().generate_points(0.005);
|
||||||
|
const Points smooth = TestableOctagramSpiral().generate_points(0.005, 1.);
|
||||||
|
|
||||||
|
REQUIRE(smooth.size() > sharp.size());
|
||||||
|
REQUIRE(smooth.front() == sharp.front());
|
||||||
|
REQUIRE(smooth.back() == sharp.back());
|
||||||
|
// The spiral alternates between 90 and 135 degree turns; both are rounded into gentle ones.
|
||||||
|
REQUIRE(sharpest_turn_cosine(sharp) < -0.7);
|
||||||
|
REQUIRE(sharpest_turn_cosine(smooth) > 0.9);
|
||||||
|
|
||||||
|
for (size_t i = 1; i < smooth.size(); ++i)
|
||||||
|
REQUIRE((smooth[i] - smooth[i - 1]).cast<double>().squaredNorm() > 0.);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("Octagram spiral smooth factor controls corner curvature", "[FillPlanePath]")
|
||||||
|
{
|
||||||
|
const Points sharp = TestableOctagramSpiral().generate_points(0.005);
|
||||||
|
const Points half_smooth = TestableOctagramSpiral().generate_points(0.005, 0.5);
|
||||||
|
const Points full_smooth = TestableOctagramSpiral().generate_points(0.005, 1.);
|
||||||
|
const Points invalid_factor = TestableOctagramSpiral().generate_points(
|
||||||
|
0.005, std::numeric_limits<double>::quiet_NaN());
|
||||||
|
|
||||||
|
REQUIRE(path_length(full_smooth) < path_length(half_smooth));
|
||||||
|
REQUIRE(path_length(half_smooth) < path_length(sharp));
|
||||||
|
REQUIRE(invalid_factor == sharp);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_CASE("Hilbert curve smooth factor controls corner curvature", "[FillPlanePath]")
|
TEST_CASE("Hilbert curve smooth factor controls corner curvature", "[FillPlanePath]")
|
||||||
{
|
{
|
||||||
const Points sharp = TestableHilbertCurve().generate_points(0.005);
|
const Points sharp = TestableHilbertCurve().generate_points(0.005);
|
||||||
|
|||||||
Reference in New Issue
Block a user