Compare commits

..

1 Commits

Author SHA1 Message Date
Ian Chua
367047f731 fix: bambu slice hover popup disappears to fast macos 2026-05-22 19:24:06 +08:00
1638 changed files with 32022 additions and 79244 deletions

View File

@@ -142,7 +142,7 @@ jobs:
flatpak:
name: "Flatpak"
container:
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-50
image: ghcr.io/flathub-infra/flatpak-github-actions:gnome-49
options: --privileged
volumes:
- /usr/local/lib/android:/usr/local/lib/android

View File

@@ -34,6 +34,46 @@ jobs:
python3 ./scripts/orca_extra_profile_check.py 2>&1 | tee ${{ runner.temp }}/extra_json_check.log
exit ${PIPESTATUS[0]}
- name: Check profile indentation
id: indentation_check
continue-on-error: true
run: |
set +e
python3 - <<'PY' 2>&1 | tee ${{ runner.temp }}/indentation_check.log
import re
from pathlib import Path
import sys
profiles_root = Path("resources/profiles")
invalid_files = []
for file_path in sorted(profiles_root.rglob("*.json")):
try:
for line_number, line in enumerate(file_path.read_text(encoding="utf-8").splitlines(), start=1):
if not line.strip():
continue
leading_ws = re.match(r"^[ \t]*", line).group(0)
if " " in leading_ws:
invalid_files.append((file_path, line_number))
break
except Exception as exc:
print(f"[ERROR] Unable to read {file_path}: {exc}")
invalid_files.append((file_path, 0))
if invalid_files:
for path, line in invalid_files:
if line > 0:
print(f"[ERROR] Space indentation found in {path}:{line}")
else:
print(f"[ERROR] Could not validate indentation in {path}")
print("Use tab indentation in profile JSON files (1 tab per indentation level).")
print("Tip: run python3 ./scripts/orca_filament_lib.py --fix --force to normalize formatting.")
sys.exit(1)
print("All profile JSON files use tab-only indentation.")
PY
exit ${PIPESTATUS[0]}
# download
- name: Download
working-directory: ${{ github.workspace }}
@@ -61,18 +101,12 @@ jobs:
./OrcaSlicer_profile_validator -p ${{ github.workspace }}/resources/profiles -l 2 2>&1 | tee ${{ runner.temp }}/validate_custom.log
exit ${PIPESTATUS[0]}
- name: Prepare PR number for comment workflow
if: ${{ always() && github.event_name == 'pull_request' }}
- name: Prepare comment artifact
if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.indentation_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
run: |
mkdir -p ${{ runner.temp }}/profile-check-results
echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt
- name: Prepare comment artifact
if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
run: |
{
# Marker matched by check_profiles_comment.yml to delete prior comments.
echo "<!-- profile-validation-comment -->"
echo "## :x: Profile Validation Errors"
echo ""
@@ -85,6 +119,15 @@ jobs:
echo ""
fi
if [ "${{ steps.indentation_check.outcome }}" = "failure" ]; then
echo "### Indentation Check Failed"
echo ""
echo '```'
head -c 30000 ${{ runner.temp }}/indentation_check.log || echo "No output captured"
echo '```'
echo ""
fi
if [ "${{ steps.validate_system.outcome }}" = "failure" ]; then
echo "### System Profile Validation Failed"
echo ""
@@ -107,8 +150,10 @@ jobs:
echo "*Please fix the above errors and push a new commit.*"
} > ${{ runner.temp }}/profile-check-results/pr_comment.md
echo "${{ github.event.pull_request.number }}" > ${{ runner.temp }}/profile-check-results/pr_number.txt
- name: Upload comment artifact
if: ${{ always() && github.event_name == 'pull_request' }}
if: ${{ always() && github.event_name == 'pull_request' && (steps.extra_json_check.outcome == 'failure' || steps.indentation_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
uses: actions/upload-artifact@v7
with:
name: profile-check-results
@@ -116,7 +161,7 @@ jobs:
retention-days: 1
- name: Fail if any check failed
if: ${{ always() && (steps.extra_json_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
if: ${{ always() && (steps.extra_json_check.outcome == 'failure' || steps.indentation_check.outcome == 'failure' || steps.validate_system.outcome == 'failure' || steps.validate_custom.outcome == 'failure') }}
run: |
echo "One or more profile checks failed. See above for details."
exit 1

View File

@@ -10,19 +10,12 @@ on:
permissions:
pull-requests: write
# Needed to delete outdated bot comments via the issues/comments endpoint.
issues: write
# Serialize handlers per source branch so parallel runs don't race delete-and-post.
concurrency:
group: ${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }}
cancel-in-progress: false
jobs:
post_comment:
name: Post PR comment
runs-on: ubuntu-24.04
if: ${{ github.event.workflow_run.event == 'pull_request' && (github.event.workflow_run.conclusion == 'success' || github.event.workflow_run.conclusion == 'failure') }}
if: ${{ github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'failure' }}
steps:
- name: Download artifact
id: download
@@ -33,14 +26,14 @@ jobs:
run-id: ${{ github.event.workflow_run.id }}
github-token: ${{ github.token }}
- name: Update PR comment
- name: Post comment on PR
if: ${{ steps.download.outcome == 'success' }}
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
run: |
if [ ! -f pr_number.txt ]; then
echo "No pr_number.txt in artifact, skipping."
if [ ! -f pr_number.txt ] || [ ! -f pr_comment.md ]; then
echo "No comment artifact found, skipping."
exit 0
fi
@@ -50,17 +43,4 @@ jobs:
exit 1
fi
# Delete prior comments matching the marker (from check_profiles.yml) or the legacy heading.
OLD_IDS=$(gh api --paginate "repos/${GH_REPO}/issues/${PR_NUMBER}/comments" \
--jq '.[] | select(.user.login == "github-actions[bot]") | select((.body | startswith("<!-- profile-validation-comment -->")) or (.body | startswith("## :x: Profile Validation Errors"))) | .id')
for comment_id in $OLD_IDS; do
echo "Deleting outdated profile-validation comment ${comment_id}"
gh api -X DELETE "repos/${GH_REPO}/issues/comments/${comment_id}" || true
done
# Post a new comment only when validation failed (pr_comment.md present).
if [ -f pr_comment.md ]; then
gh pr comment "$PR_NUMBER" --body-file pr_comment.md
else
echo "Validation succeeded; cleaned up prior comments without posting."
fi
gh pr comment "$PR_NUMBER" --body-file pr_comment.md

3
.gitignore vendored
View File

@@ -45,5 +45,4 @@ test.js
.clangd
internal_docs/
*.flatpak
/flatpak-repo/
*.pyc
/flatpak-repo/

View File

@@ -199,22 +199,22 @@ echo -e "${GREEN}All required dependencies found${NC}"
# Install runtime and SDK if requested
if [[ "$INSTALL_RUNTIME" == true ]]; then
echo -e "${YELLOW}Installing GNOME runtime and SDK...${NC}"
flatpak install --user -y flathub org.gnome.Platform//50
flatpak install --user -y flathub org.gnome.Sdk//50
flatpak install --user -y flathub org.gnome.Platform//49
flatpak install --user -y flathub org.gnome.Sdk//49
fi
# Check if required runtime is available
if ! flatpak info --user org.gnome.Platform//50 &> /dev/null; then
echo -e "${RED}Error: GNOME Platform 50 runtime is not installed.${NC}"
if ! flatpak info --user org.gnome.Platform//49 &> /dev/null; then
echo -e "${RED}Error: GNOME Platform 49 runtime is not installed.${NC}"
echo "Run with -i flag to install it automatically, or install manually:"
echo "flatpak install --user flathub org.gnome.Platform//50"
echo "flatpak install --user flathub org.gnome.Platform//49"
exit 1
fi
if ! flatpak info --user org.gnome.Sdk//50 &> /dev/null; then
echo -e "${RED}Error: GNOME SDK 50 is not installed.${NC}"
if ! flatpak info --user org.gnome.Sdk//49 &> /dev/null; then
echo -e "${RED}Error: GNOME SDK 49 is not installed.${NC}"
echo "Run with -i flag to install it automatically, or install manually:"
echo "flatpak install --user flathub org.gnome.Sdk//50"
echo "flatpak install --user flathub org.gnome.Sdk//49"
exit 1
fi

View File

@@ -1,11 +1,5 @@
set(_eigen_extra_flags "")
if (MSVC)
set(_eigen_extra_flags "-DCMAKE_CXX_FLAGS:STRING=/bigobj")
endif ()
orcaslicer_add_cmake_project(Eigen
URL https://gitlab.com/libeigen/eigen/-/archive/5.0.1/eigen-5.0.1.zip
URL_HASH SHA256=0dbb1f9e3aaad66f352c03227d8c983f6f0b49e0b07e71a7300f4abcc01aee12
CMAKE_ARGS "${_eigen_extra_flags}"
DEPENDS dep_Boost dep_GMP dep_MPFR
)

View File

@@ -1,668 +0,0 @@
# OrcaSlicer UI Automation Protocol (v1.0.0)
OrcaSlicer ships an **opt-in, localhost-only JSON-RPC server** that lets external
scripts introspect, drive, and screenshot the running OrcaSlicer GUI. It is built
for end-to-end testing and automation: a script can enumerate the live widget
tree, click buttons, type text, send keyboard shortcuts, wait for UI state, query
high-level application state, load models/projects into the running instance,
switch the active view/tab, and capture window images (the on-screen capture
includes the 3D viewport).
This document is the protocol reference. It describes activation, the transport,
the JSON-RPC envelope, every method, the unified node shape, the target/locator
model, error codes, the set of instrumented automation ids, ImGui specifics,
platform caveats, a quick-start snippet, and planned future work.
---
## 1. Overview & activation
The automation server is **OFF by default**. It is enabled with two
command-line flags:
| Flag | Meaning |
|---|---|
| `--automation-server` | Enable the automation server. |
| `--automation-server-port=PORT` | Override the listening port. Optional; default is **13619**. |
Example:
```bash
OrcaSlicer --automation-server --automation-server-port=13619 model.stl
```
The server binds to **`127.0.0.1` only** (the loopback interface). It is never
exposed on an external network interface.
**Security note (v1):** there is **no authentication token** in v1. The localhost
bind is the *only* security boundary. Any process able to run code on the machine
can connect to the port and drive the GUI — including injecting mouse and keyboard
input — while the server is enabled. The feature is intended for testing and
automation environments, not for production or shared/multi-user machines.
When the server is enabled, OrcaSlicer emits a `warning`-level log line at startup
to make the active input-injection surface obvious in logs, for example:
```
UI automation server ENABLED ... input injection is active
```
---
## 2. Transport
The server speaks **HTTP/1.1** over the loopback TCP socket:
| Request | Response |
|---|---|
| `POST /jsonrpc` with a JSON-RPC 2.0 request body | A JSON-RPC 2.0 response with `Content-Type: application/json`. |
| `GET /` | A plain-text health page: `OrcaSlicer automation server v1.0.0` (`Content-Type: text/plain`). |
| Anything else | HTTP `404 Not Found`. |
The server is **single-client / serialized** in v1: it handles one request at a
time on its own dedicated I/O thread. Connections are not kept alive; each request
is answered and the socket is closed. Clients should issue requests sequentially.
---
## 3. JSON-RPC envelope
The protocol follows **JSON-RPC 2.0**.
**Request:**
```json
{ "jsonrpc": "2.0", "id": <id>, "method": "<method>", "params": { ... } }
```
- `params` may be omitted; the server treats a missing `params` as an empty object.
**Success response:**
```json
{ "jsonrpc": "2.0", "id": <id>, "result": { ... } }
```
**Error response:**
```json
{ "jsonrpc": "2.0", "id": <id>, "error": { "code": <int>, "message": "<string>" } }
```
The request `id` is echoed back in the response. When the request has no `id`, or
when the request body cannot be parsed as JSON, the response `id` is `null`.
---
## 4. Methods
There are 12 methods. Capabilities advertised by `automation.version` list the 11
callable feature methods (every method except `automation.version` itself).
### `automation.version`
Returns server identity and the list of supported methods. Takes no parameters.
**Result:**
```json
{
"version": "1.0.0",
"protocol": "2.0",
"capabilities": [
"tree.dump", "tree.find", "widget.get", "input.click", "input.type",
"input.key", "sync.wait_for", "app.state", "screenshot.window", "file.open",
"view.select"
]
}
```
### `tree.dump`
Snapshot the live UI tree as a single root node with nested children.
**Params (all optional):**
| Param | Type | Default | Meaning |
|---|---|---|---|
| `root` | string (id or path) | full tree | Root the dump at the node with this id/path. |
| `max_depth` | int | `-1` | Maximum depth to descend. `-1` = unlimited. |
| `visible_only` | bool | `false` | When true, omit non-visible nodes. |
| `include_imgui` | bool | `true` | When true, include ImGui items. |
**Result:** the serialized root [node](#5-unified-node-shape), with `children`
included.
### `tree.find`
Find all nodes matching a [target predicate](#6-target--locator).
**Params:** a target predicate — any combination of `name`, `class`, `label`,
`value`, `backend` (provided fields are ANDed). The params object is the target
itself (it is *not* wrapped in a `target` key for this method).
**Result:** a **flat JSON array** of matching nodes. The nodes in this array are
returned **without** their `children` (use `widget.get`/`tree.dump` to descend).
### `widget.get`
Fetch a single node by [target](#6-target--locator).
**Params:**
| Param | Type | Required | Meaning |
|---|---|---|---|
| `target` | object | yes | Target spec (id / path / predicate). |
**Result:** a single [node](#5-unified-node-shape), with its `children` included.
**Errors:** `1001` if the target is **not found** *or* **ambiguous** (more than one
match).
### `input.click`
Click a resolved, actionable node.
**Params:**
| Param | Type | Default | Meaning |
|---|---|---|---|
| `target` | object | required | Target spec; must resolve to exactly one node. |
| `button` | string | `"left"` | `"left"`, `"right"`, or `"middle"`. |
| `double` | bool | `false` | Double-click when true. |
| `modifiers` | array of string | `[]` | Held modifiers: any of `"ctrl"`, `"shift"`, `"alt"`, `"cmd"` (`"meta"` is accepted as an alias of `"cmd"`). |
**Result:** `{ "ok": true }`.
**Errors:** `1001` not found / ambiguous; `1002` if the target is disabled or
hidden (not actionable). The click path raises and focuses the target's top-level
window before injecting the click.
### `input.type`
Type text into the currently focused control.
**Params:**
| Param | Type | Required | Meaning |
|---|---|---|---|
| `text` | string | yes | The text to type. |
| `target` | object | no | If given, this node is clicked first (to focus it) before typing. |
**Result:** `{ "ok": true }`.
**Errors:** if `target` is supplied, the same actionability errors as
`input.click` apply (`1001` / `1002`).
### `input.key`
Send a key chord (a key plus optional modifiers) to the focused window.
**Params:**
| Param | Type | Required | Meaning |
|---|---|---|---|
| `keys` | string or array | yes | Either a `"+"`-joined string like `"ctrl+s"`, or an array like `["ctrl", "s"]`. The last token is the key; earlier tokens are modifiers. |
**Result:** `{ "ok": true }`.
**Key names must be lowercase.** Recognized key names include `"enter"`, `"tab"`,
`"esc"`, `"space"`, `"delete"`, `"backspace"`, `"f5"` (and other function keys),
and single characters (e.g. `"s"`, `"a"`). Recognized modifiers are `"ctrl"`,
`"shift"`, `"alt"`, `"cmd"` (with `"meta"` as an alias for `"cmd"`).
**Unrecognized or uppercase key names are silently ignored** — no error is
returned, the key simply does not fire. Use lowercase names exclusively.
### `sync.wait_for`
Poll the UI until a target node reaches a desired state, or time out. This is the
preferred way to synchronize with asynchronous UI changes (it replaces fragile
fixed sleeps). Internally it repeatedly refreshes and dumps the tree, re-resolves
the target, and evaluates the requested state until it is satisfied.
**Params:**
| Param | Type | Default | Meaning |
|---|---|---|---|
| `target` | object | required | Target spec. |
| `state` | string | required | One of `"exists"`, `"visible"`, `"enabled"`, `"value"`. |
| `value` | string | — | Required when `state` is `"value"`; the expected value to match. |
| `timeout_ms` | int | `5000` | Maximum time to wait, in milliseconds. |
| `poll_ms` | int | `100` | Poll interval, in milliseconds (minimum 1). |
State semantics:
- `exists` — the target resolves to a node.
- `visible` — the node exists and is visible.
- `enabled` — the node exists and is **both enabled and visible**.
- `value` — the node has a value and that value equals the supplied `value`.
**Result:** `{ "ok": true, "elapsed_ms": <int> }`.
**Errors:** `1003` on timeout (the state was not reached within `timeout_ms`).
### `app.state`
Return a high-level application-state snapshot. Takes no parameters.
**Result:**
```json
{
"active_tab": "<string>",
"project_loaded": <bool>,
"slicing": <bool>,
"slice_progress": <int>,
"foreground": <bool>,
"modal_dialog": "<string>"
}
```
| Field | Meaning |
|---|---|
| `active_tab` | The active top-level tab/page. |
| `project_loaded` | Whether a project/model is currently loaded. |
| `slicing` | Whether slicing is currently in progress. |
| `slice_progress` | Slicing progress (`-1` when unknown). |
| `foreground` | Whether the main window is in the foreground. |
| `modal_dialog` | Present only when a modal dialog is active; identifies it. Omitted otherwise. |
### `screenshot.window`
Capture a window as a PNG, exactly as it appears on screen.
**Params:**
| Param | Type | Default | Meaning |
|---|---|---|---|
| `target` | object | main frame | If given, capture this window; otherwise capture the main frame. |
**Result:** `{ "png_base64": "<base64 PNG>", "width": <int>, "height": <int> }`.
**Errors:** `1005` on screenshot failure; `1001` if a supplied `target` is not
found or ambiguous.
**How it works:** the window's on-screen rectangle is read back from the
DWM-composited desktop framebuffer (`wxScreenDC`), so the capture includes every
native child control, the OpenGL 3D viewport, and ImGui overlays — it is a faithful
image of what the user sees. (Capturing the parent window's own client DC instead
would clip out child HWNDs and the GL surface, leaving them black; that is why this
method reads from the screen.)
**Caveats:**
- The window must be **visible and unobscured**. Because the source is the on-screen
framebuffer, any overlapping window occludes the captured region. The backend
raises the target window before capturing.
- **HiDPI:** the reported `width`/`height` come from the window's logical client size,
while the screen framebuffer is in physical pixels. On per-monitor-DPI displays the
two can differ; the capture may be cropped or scaled relative to the logical size.
- Because the capture is the live on-screen image, the 3D content reflects the
**current view**: the model in the 3D editor, or the gcode toolpaths in Preview
after a slice. There is no separate offscreen 3D-render method — the window
capture already includes whatever the GL canvas is showing.
### `file.open`
Load one or more files into the **already-running** instance at runtime, by calling
`Plater::load_files(...)` directly on the GUI thread. This is the supported way to add
or swap a model without relaunching the process. Loading is **synchronous**: when the
call returns `ok: true`, `app.state().project_loaded` is already `true` (no polling
race).
**Params:**
| Param | Type | Required | Meaning |
|---|---|---|---|
| `paths` | string or array of strings | yes | One or more **absolute** file paths. A bare string is accepted and treated as a one-element list. Paths are read from the **host (server) filesystem** — client and server are localhost-only. |
`.3mf` files are routed as projects and meshes as models automatically, based on file
content (the same default strategy as drag-drop); there is no `as_project` flag in v1.
**Result:** `{ "ok": true, "loaded": <int> }`, where `loaded` is the number of objects
added to the scene (`load_files(...).size()`).
**Errors:**
- `-32602` (invalid params) — `paths` is missing, is not a string/array, contains a
non-string entry, or yields no non-empty path.
- `1007` (load failed) — `load_files` returned empty or threw (file not found, parse
error, or unsupported format).
- `1004` (GUI busy) — the GUI-thread marshal timed out. An extremely large model can
exceed the marshal timeout and surface here; documented, not mitigated in v1.
### `view.select`
Switch the main window to a top-level view/tab at runtime. Useful to put the UI in a
known state before other actions — e.g. switch to **Prepare** before loading a model,
or to **Preview** after slicing.
**Params:**
| Param | Type | Required | Meaning |
|---|---|---|---|
| `view` | string | yes | The target view. One of: `home`, `prepare` (3D editor), `preview` (sliced G-code), `device`, `multi_device`, `project`, `calibration`. |
**Result:** `{ "ok": true, "view": <string>, "index": <int> }`, where `index` is the
resulting tab index (it can vary with layout, since some tabs — e.g. `multi_device`
are only present in certain configurations).
**Errors:**
- `-32602` (invalid params) — `view` is missing, is not a string, or is empty.
- `1001` (not found) — the view name is unknown, or that view is not available in the
current layout (for example `prepare`/`preview` in G-code-viewer mode, or
`multi_device` when multi-device is disabled).
- `1004` (GUI busy) — the GUI-thread marshal timed out.
---
## 5. Unified node shape
Both wx widgets and ImGui items are reported with the same node schema:
```json
{
"backend": "wx" | "imgui",
"id": "<string>",
"path": "<string>",
"class": "<string>",
"label": "<string>",
"rect": { "x": <int>, "y": <int>, "w": <int>, "h": <int> },
"enabled": <bool>,
"visible": <bool>,
"value": "<string>",
"children": [ <node>, ... ]
}
```
| Field | Meaning |
|---|---|
| `backend` | `"wx"` for native wxWidgets controls, `"imgui"` for immediate-mode ImGui items. |
| `id` | The automation id when one is set, otherwise a derived id. For ImGui items the `path` doubles as the `id`. |
| `path` | Positional path, e.g. `"MainFrame/Panel[2]/Button[0]"`. For ImGui items: `"ImGui/<window>/<label>"`. |
| `class` | wx class name, or the ImGui item type. |
| `label` | The control's label/caption. May include an ImGui `##`-id suffix for ImGui items. |
| `rect` | Bounding rectangle in **screen coordinates**. |
| `enabled` | Whether the control is enabled. |
| `visible` | Whether the control is visible. |
| `value` | The control's value (text/choice/check/slider, etc.). **Omitted entirely** when the control has no applicable value. |
| `children` | Child nodes. **wx only**, and present only when children are included (e.g. `tree.dump`, `widget.get`). ImGui items are flat (no children) and are listed under their window. |
Notes:
- The `value` key is **omitted** (not `null`) when the control has no value.
- `children` is present only for wx nodes when children are requested; ImGui nodes
never carry `children`.
---
## 6. Target / locator
Most methods accept a **target** object that identifies one or more nodes. A
target may specify:
| Field | Meaning |
|---|---|
| `id` | Exact automation id. |
| `path` | Exact positional path. |
| `name` | Predicate: matches either the node's `id` **or** its `label`. |
| `class` | Predicate: exact class name. |
| `label` | Predicate: exact label. |
| `value` | Predicate: node has a value and it equals this string. |
| `backend` | Predicate: `"wx"` or `"imgui"`. |
**Resolution order:** **`id``path` → predicate.**
- If `id` is present, only `id` is used (exact match).
- Else if `path` is present, only `path` is used (exact match).
- Else the predicate fields (`name`, `class`, `label`, `value`, `backend`) are
used, and all provided predicate fields are **ANDed** together.
Action methods (`input.click`, `input.type` with a target, `widget.get`, and
single-target `screenshot.window`) require a **unique** match. If the target
resolves to zero matches or more than one match, the call fails with error `1001`
(not found / ambiguous). `tree.find` is the exception: it returns *all* matches as
an array and never errors on ambiguity.
---
## 7. Error codes
Standard JSON-RPC codes:
| Code | Meaning |
|---|---|
| `-32700` | Parse error — the request body was not valid JSON. |
| `-32600` | Invalid request — missing/invalid `method`. |
| `-32601` | Method not found — unknown method name. |
| `-32602` | Invalid params — missing/invalid parameters for the method. |
Application-specific codes:
| Code | Meaning |
|---|---|
| `1001` | Widget/target not found **or** ambiguous (more than one match). |
| `1002` | Not actionable — the target is disabled or hidden. |
| `1003` | Wait timeout — `sync.wait_for` did not reach the requested state in time. |
| `1004` | GUI thread busy / timeout — a backend call could not be marshaled onto the GUI thread in time (wedged GUI). |
| `1005` | Screenshot failed. |
| `1006` | Disabled. |
| `1007` | Load failed — `file.open`'s `load_files` returned empty or threw (not found, parse error, unsupported format). |
---
## 8. Automation-id naming conventions & instrumented ids
Stable automation ids follow these prefix conventions:
| Prefix | Used for |
|---|---|
| `btn_` | Buttons |
| `combo_` | Preset combo boxes |
| `tab_` | Tabs |
| `canvas_` | Canvases |
| `dlg_` | Dialog buttons |
### Instrumented ids (as-built in v1)
The following controls currently carry stable automation ids:
| id | Control | Note |
|---|---|---|
| `btn_slice` | Slice-plate button | |
| `btn_export` | Print / Export button | Multi-purpose: the action (Print plate / Export G-code / Send) depends on the current mode. |
| `tab_device` | Device / Monitor tab (`MonitorPanel`) | |
| `combo_printer` | Printer preset combo (sidebar) | |
| `combo_filament` | Filament preset combo (sidebar) | First filament row only; extra multi-material rows are not instrumented. |
| `canvas_3d` | 3D editor GL canvas | |
### Controls NOT instrumented in v1
Several controls are intentionally **not** instrumented in v1 because they have no
stable `wxWindow` target to attach an id to:
- **`combo_process`** — process settings are not a sidebar combo box in the current
OrcaSlicer layout, so there is no combo control to instrument.
- **`btn_add`** — the add/import-object control is a `GLToolbar` item rendered
*inside* the GL canvas, not a `wxWindow`.
- **`tab_prepare` / `tab_preview`** — the Prepare and Preview notebook pages are
both backed by the **same** window, and the per-tab buttons are private; there is
no distinct stable window to target.
For controls that are not instrumented, scripts should fall back to class / label /
path lookup (for wx controls) or ImGui-item lookup (for ImGui controls).
---
## 9. ImGui notes
ImGui is **immediate-mode**: an item is addressable only while it is being drawn in
the current frame. The automation backend records ImGui items each frame, and a
`refresh_ui` is forced before every read or action so that the latest frame's items
are captured.
Consequences and conventions:
- Use [`sync.wait_for`](#syncwait_for) to wait for a transient gizmo or panel item
to appear before acting on it.
- ImGui items are reported with `backend: "imgui"`, a `path` of the form
`ImGui/<window>/<label>`, and that **path doubles as the item's `id`** in v1.
- ImGui items are **flat** — they have no `children` and are listed under their
window.
- Labels may include ImGui `##`-id suffixes (the part after `##` that ImGui uses to
disambiguate identically labeled widgets).
- Raw `ImGui::` gizmos that are *not* routed through the instrumented
`ImGuiWrapper` widgets (for example some Emboss / SVG / Text gizmo controls) are
only covered at the **window level** in v1; their individual sub-items are not
enumerated.
---
## 10. Platform & display caveats
- **Input requires a focused, visible window.** OS-level input injection uses
`wxUIActionSimulator`, which requires a focused, visible window. The click path
raises and focuses the target's top-level window first.
- **Linux CI needs a display.** There must be an X display available; wrap test
runs with `xvfb-run` (for example, `xvfb-run -a python example_slice.py ...`).
- **Input is asynchronous.** Do **not** rely on fixed sleeps. Use
[`sync.wait_for`](#syncwait_for) — for example, wait for `btn_export` to become
`enabled` after slicing completes — rather than sleeping for a guessed duration.
- **`screenshot.window` reads the screen.** It captures the on-screen, DWM-composited
framebuffer, so the target window must be visible and unobscured, and the result is
in physical pixels (see HiDPI caveat under [`screenshot.window`](#screenshotwindow)).
The capture includes the GL 3D viewport as currently shown (model or toolpaths).
- **Single-client / serialized.** v1 handles one request at a time; issue requests
sequentially from a single client.
- **GUI-thread marshaling.** Every backend call is marshaled onto the GUI thread
with a timeout. A wedged or unresponsive GUI returns error `1004`.
---
## 11. Quick start
Using the reference client in `tools/automation/orca_automation.py`:
```python
from orca_automation import OrcaClient
orca = OrcaClient(port=13619)
print(orca.version()) # {'version': '1.0.0', ...}
orca.select_view("prepare") # switch to the 3D editor
orca.open(r"C:\models\part.stl") # load a model at runtime (synchronous)
orca.click({"id": "btn_slice"}) # start slicing the plate
orca.wait_for({"id": "btn_export"}, # wait until slicing finishes
state="enabled", timeout_ms=180000)
orca.select_view("preview") # switch to the sliced G-code preview
png = orca.screenshot() # on-screen capture (incl. 3D view)
with open("window.png", "wb") as f:
f.write(png)
```
For a full, runnable end-to-end example — launching OrcaSlicer with the automation
flags, loading a model, slicing, waiting for completion, and saving a window PNG —
see `tools/automation/example_slice.py`.
---
## 12. Future work
Planned enhancements beyond v1:
- **Authentication token** plus a Preferences toggle to enable/disable the server
from the GUI.
- **WebSocket push events** for real-time UI/state notifications (instead of
polling).
- **Per-item ImGui gizmo instrumentation** so individual gizmo sub-controls (Emboss
/ SVG / Text, etc.) are addressable, not just at the window level.
- **More widget ids** — the process combo, the add/import button, and the
Prepare/Preview tabs once they expose stable windows.
- An **MCP wrapper** to expose the automation surface to model-context tooling.
---
## Verification (v1)
This section records the final regression gate for the v1 feature: confirmation
that the protocol core is covered by unit tests, that the existing test suites
are unaffected, and that the **disabled path (automation OFF, the default) is a
true no-op** — zero new threads, zero socket binds, zero allocations, and zero
behavior change.
### Unit-suite results (Release, Windows / MSVC, Ninja Multi-Config)
| Suite | Result |
|---|---|
| `automation` (protocol core) | **32 / 32 passed** |
| `libslic3r` (most affected by the additive `PrintConfig.cpp` CLI options) | **99 / 99 passed** |
| `fff_print` | **14 / 14 passed** |
| `libnest2d` | **14 / 14 passed** |
| `sla_print` | **21 / 21 passed** |
| `slic3rutils` | 3 / 5 passed — 2 pre-existing `[OrcaCloudServiceAgent]` SEGFAULTs, **unrelated to automation** (see note) |
> The two `slic3rutils` failures are `Orca cloud flat/nested session resolves
> display name consistently`. They exercise `Slic3r::OrcaCloudServiceAgent`, which
> the automation branch does **not** touch (verified via `git diff --stat
> main...HEAD` — no change to `src/slic3r/Utils/OrcaCloudServiceAgent.*` or
> `tests/slic3rutils/*`). They are pre-existing and not a regression introduced by
> this feature.
### Static disabled-path audit (the core regression guarantee)
Verified by code reading that with no `--automation-server` flag:
- **Flag defaults off.** `m_automation_port` defaults to `0`
(`src/slic3r/GUI/GUI_App.hpp:249`); `is_automation_enabled()` returns
`m_automation_port > 0` (`GUI_App.hpp:386`) → `false` by default.
- **No server / thread / socket.** `post_init()` calls
`start_automation_server()` **only** when
`init_params->automation_port > 0` (`src/slic3r/GUI/GUI_App.cpp:737-740`), and
`start_automation_server()` itself early-returns when `m_automation_port <= 0`
(`GUI_App.cpp:7097`). The backend / dispatcher / beast server objects are
constructed nowhere else → no `orca_automation` thread and no localhost bind
when the flag is absent.
- **Recording hooks short-circuit.** `ImGuiWrapper::automation_record_last_item`
has as its **first statement** `if (!wxGetApp().is_automation_enabled())
return;` (`src/slic3r/GUI/ImGuiWrapper.cpp:576-577`) — a single bool check, no
`ImGuiItemRecord` allocation and no `ImGuiItemTable` access on the disabled
path. In `ImGuiWrapper::render()` the window-enumeration loop and
`swap_frame()` are fully wrapped in `if (wxGetApp().is_automation_enabled())`
(`ImGuiWrapper.cpp:599-611`); when off, `render()` is its original
`ImGui::Render()` + `render_draw_data()` plus one bool check.
- **Instrumentation is inert.** The ~7 `set_automation_id(...)` calls
(`MainFrame.cpp:1330,1389,1841,1842`; `Plater.cpp:1772,2172,5068`) only store a
pointer into a static registry and bind a `wxEVT_DESTROY` pruning handler
(`src/slic3r/GUI/Automation/AutomationRegistry.cpp:24-36`). The registry is
**read** only via `window_for_automation_id` / `automation_id_of`, which are
called solely by the backend while the server is running → harmless when off.
- **CLI options are purely additive.** `automation_server` (coBool, default
`false`) and `automation_server_port` (coInt, default `13619`) are new `add()`
entries appended after `enable_timelapse`
(`src/libslic3r/PrintConfig.cpp:10794-10805`); no existing option is changed.
`GUI_InitParams::automation_port` defaults to `0`
(`src/slic3r/GUI/GUI_Init.hpp:37`) and is set only when `--automation-server`
is supplied (`src/OrcaSlicer.cpp:1345-1348`).
**Conclusion:** with automation OFF (the default), the feature allocates nothing
and changes nothing — the only added cost on any hot path is a single boolean
comparison.
### Deferred manual runtime checks (require a display / Xvfb)
These need a live GUI and cannot be run headlessly in CI; they are the manual
acceptance steps:
1. Launch **without** `--automation-server``curl http://127.0.0.1:13619/`
fails to connect (no listener); no `orca_automation` thread exists.
2. Launch **with** `--automation-server --automation-server-port=13619`
`GET /` returns the health text; `POST /jsonrpc {"method":"automation.version"}`
returns version / protocol / capabilities; `widget.get {"target":{"id":"btn_slice"}}`
returns a node with a sensible screen rect.
3. Interactive sanity: open a gizmo / move sliders with automation OFF → no
visual or behavior change.
See `tools/automation/example_slice.py` for the runnable end-to-end path.

View File

@@ -1,608 +0,0 @@
# `file.open` Automation Method Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add a `file.open` JSON-RPC automation method that loads one or more files into an already-running OrcaSlicer instance by calling `Plater::load_files(...)` synchronously on the GUI thread.
**Architecture:** Follows the existing `screenshot.window` / `app.state` method pattern. A new pure-virtual `open_files(paths)` is added to the wx-free `IUiBackend` interface; `WxUiBackend` implements it via the existing `run_on_gui(...)` GUI-thread marshal calling `Plater::load_files`; the `JsonRpcDispatcher` gains a `file.open` route, a param-parsing helper, and a new `kErrLoadFailed = 1007` error code. The unit-testable surface (dispatcher + param validation + routing) is driven against `MockUiBackend`.
**Tech Stack:** C++17, nlohmann::json, Catch2 v2 (`catch_all.hpp` / `Catch2WithMain`), wxWidgets, CMake + Ninja Multi-Config. Python 3 reference client (stdlib only).
---
## Design-spec note (resolve before coding)
The design spec's error table reads `1002 | kInvalidParams | paths missing/empty…`, but in the codebase `kInvalidParams` is the standard JSON-RPC code **`-32602`**, while `1002` is `kErrNotActionable`. The spec's **Constant column (`kInvalidParams`) is authoritative** and matches every other param-validation path in the dispatcher (e.g. `m_input_type` throws `kInvalidParams` for a bad `text`). This plan therefore validates `file.open` params with **`kInvalidParams` (-32602)**, exactly like the existing handlers, and the tests assert `== kInvalidParams`. The literal "1002" in the spec is a typo; do not emit code 1002 for param errors.
## File Structure
| File | Change | Responsibility |
|---|---|---|
| `src/slic3r/GUI/Automation/IUiBackend.hpp` | modify | Add pure-virtual `int open_files(paths)` to the backend abstraction (stays wx-free). |
| `src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp` | modify | Add `kErrLoadFailed = 1007` constant + `m_file_open` declaration. |
| `src/slic3r/GUI/Automation/JsonRpcDispatcher.cpp` | modify | Add `parse_paths` helper, `m_file_open` body, dispatch route, capabilities entry. |
| `src/slic3r/GUI/Automation/WxUiBackend.hpp` | modify | Declare `open_files` override. |
| `src/slic3r/GUI/Automation/WxUiBackend.cpp` | modify | Implement `open_files` via `run_on_gui``Plater::load_files`. |
| `tests/automation/MockUiBackend.hpp` | modify | `open_files` override: record paths + return-count + fail knob. |
| `tests/automation/test_dispatcher.cpp` | modify | Catch2 tests for routing, string/array, validation, failure, capabilities. |
| `tools/automation/orca_automation.py` | modify | `open(self, paths)` client wrapper. |
| `tools/automation/example_slice.py` | modify | Launch without a model arg, then `orca.open([model])`. |
| `doc/automation.md` | modify | Document the method, capabilities, error `1007`. |
**Build/test layout:** Ninja Multi-Config in `build/`. The unit suite target is `automation_tests`; its sources (`tests/automation/CMakeLists.txt`) compile `JsonRpcDispatcher.cpp` + `MockUiBackend` but **not** `WxUiBackend.cpp`. So dispatcher/mock changes are fully unit-testable headlessly; `WxUiBackend.cpp` is verified by the full app build only.
---
## Task 1: Extend the backend abstraction (interface + mock + error code)
Adds the `open_files` contract so tests can be written. Adding a pure virtual to `IUiBackend` forces every implementation to provide it — in the unit-test target that is only `MockUiBackend`, so this task keeps the `automation_tests` build green.
**Files:**
- Modify: `src/slic3r/GUI/Automation/IUiBackend.hpp`
- Modify: `src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp:19`
- Modify: `tests/automation/MockUiBackend.hpp`
- [ ] **Step 1: Add the error constant**
In `src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp`, after the existing `kErrDisabled` line (currently line 19), add:
```cpp
constexpr int kErrDisabled = 1006;
constexpr int kErrLoadFailed = 1007; // file.open: load_files returned empty / threw
```
- [ ] **Step 2: Add the pure-virtual `open_files` to the interface**
In `src/slic3r/GUI/Automation/IUiBackend.hpp`, inside `class IUiBackend`, immediately after the `screenshot_window` pure virtual (currently line 97), add:
```cpp
// Load one or more files (absolute paths) into the running instance on the GUI
// thread. Returns the number of objects added to the scene (load_files(...).size()).
// Throws AutomationError(kErrLoadFailed) when nothing loads. Header stays wx-free:
// the concrete LoadStrategy is chosen inside WxUiBackend, not exposed here.
virtual int open_files(const std::vector<std::string>& paths) = 0;
```
- [ ] **Step 3: Implement `open_files` in the mock with record + knobs**
In `tests/automation/MockUiBackend.hpp`: add an include for the error constant near the top (after the `IUiBackend.hpp` include on line 2):
```cpp
#include "slic3r/GUI/Automation/IUiBackend.hpp"
#include "slic3r/GUI/Automation/JsonRpcDispatcher.hpp" // kErrLoadFailed
```
Add recorded-call + canned-output members. After the `screenshot_window_count` recorded field (line 20) add:
```cpp
int screenshot_window_count = 0;
std::vector<std::vector<std::string>> opened_paths; // paths of each open_files()
```
After the `click_result` canned field (line 26) add:
```cpp
bool click_result = true;
int open_return_count = 0; // value open_files() returns
bool open_should_fail = false; // when true, open_files() throws kErrLoadFailed
```
Add the override next to the other overrides, after `screenshot_window` (lines 49-51):
```cpp
PngImage screenshot_window(const UiNode*) override {
++screenshot_window_count; return canned_png;
}
int open_files(const std::vector<std::string>& paths) override {
opened_paths.push_back(paths);
if (open_should_fail)
throw AutomationError(kErrLoadFailed, "mock load failed");
return open_return_count;
}
```
- [ ] **Step 4: Build the unit-test target to confirm it still compiles**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests`
Expected: build succeeds (the new pure virtual is satisfied by the mock; no behavior change yet).
- [ ] **Step 5: Commit**
```bash
git add src/slic3r/GUI/Automation/IUiBackend.hpp src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp tests/automation/MockUiBackend.hpp
git commit -m "feat(automation): add open_files to backend interface + kErrLoadFailed (1007)"
```
---
## Task 2: `file.open` dispatcher handler (parse, route, validate, fail)
Implements the full JSON-RPC handler against the mock: param parsing (string or array), validation, routing to `open_files`, and `kErrLoadFailed` propagation.
**Files:**
- Modify: `src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp:49` (declaration)
- Modify: `src/slic3r/GUI/Automation/JsonRpcDispatcher.cpp`
- Test: `tests/automation/test_dispatcher.cpp`
- [ ] **Step 1: Write the failing happy-path test (array of paths)**
Append to `tests/automation/test_dispatcher.cpp`:
```cpp
TEST_CASE("file.open with an array of paths routes to backend", "[automation][rpc]") {
MockUiBackend mock;
mock.open_return_count = 3;
JsonRpcDispatcher d(mock);
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",1},{"method","file.open"},
{"params",{{"paths", json::array({"C:/abs/a.stl","C:/abs/b.stl"})}}}});
CHECK(resp.at("result").at("ok") == true);
CHECK(resp.at("result").at("loaded") == 3);
REQUIRE(mock.opened_paths.size() == 1);
REQUIRE(mock.opened_paths[0].size() == 2);
CHECK(mock.opened_paths[0][0] == "C:/abs/a.stl");
CHECK(mock.opened_paths[0][1] == "C:/abs/b.stl");
}
```
- [ ] **Step 2: Run the test to verify it fails**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests && build/tests/automation/RelWithDebInfo/automation_tests.exe "file.open with an array of paths routes to backend"`
Expected: FAIL — `file.open` is an unknown method, so the response carries `error.code == -32601` and has no `result` (the `resp.at("result")` access throws). (If the exe path differs on your machine, locate it with `find build -iname automation_tests.exe`.)
- [ ] **Step 3: Declare the handler**
In `src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp`, after the `m_screenshot_window` declaration (currently line 49) add:
```cpp
nlohmann::json m_screenshot_window(const nlohmann::json& params);
nlohmann::json m_file_open(const nlohmann::json& params);
```
- [ ] **Step 4: Add the `parse_paths` helper and `m_file_open` body**
In `src/slic3r/GUI/Automation/JsonRpcDispatcher.cpp`, add a `parse_paths` helper. Place it in the anonymous namespace that also holds `parse_keys` — insert it right before that namespace's closing `} // namespace` (currently line 130):
```cpp
// "paths" may be a single string ("C:/a.stl") or an array of strings. Returns the
// non-empty absolute paths; throws kInvalidParams when paths is missing, not a
// string/array, contains a non-string entry, or yields no non-empty path.
std::vector<std::string> parse_paths(const nlohmann::json& params) {
if (!params.is_object() || !params.contains("paths"))
throw AutomationError(kInvalidParams, "file.open requires 'paths'");
const auto& p = params.at("paths");
std::vector<std::string> out;
if (p.is_string()) {
out.push_back(p.get<std::string>());
} else if (p.is_array()) {
for (const auto& e : p) {
if (!e.is_string())
throw AutomationError(kInvalidParams, "'paths' entries must be strings");
out.push_back(e.get<std::string>());
}
} else {
throw AutomationError(kInvalidParams, "'paths' must be a string or array");
}
out.erase(std::remove_if(out.begin(), out.end(),
[](const std::string& s) { return s.empty(); }),
out.end());
if (out.empty())
throw AutomationError(kInvalidParams, "'paths' is empty");
return out;
}
```
(`<algorithm>` for `std::remove_if` is already included at the top of the file, line 4.)
Add the handler body next to the other handlers. After `m_screenshot_window` (currently ends line 343, just before the final `}}} // namespace`), add:
```cpp
nlohmann::json JsonRpcDispatcher::m_file_open(const nlohmann::json& params) {
const std::vector<std::string> paths = parse_paths(params);
const int loaded = m_backend.open_files(paths);
return { {"ok", true}, {"loaded", loaded} };
}
```
- [ ] **Step 5: Add the dispatch route**
In `JsonRpcDispatcher::dispatch`, after the `screenshot.window` route (currently line 195) add:
```cpp
if (method == "screenshot.window") return make_result(id, m_screenshot_window(params));
if (method == "file.open") return make_result(id, m_file_open(params));
```
- [ ] **Step 6: Run the happy-path test to verify it passes**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests && build/tests/automation/RelWithDebInfo/automation_tests.exe "file.open with an array of paths routes to backend"`
Expected: PASS — 1 test case, all assertions passed.
- [ ] **Step 7: Add the remaining handler tests (string, validation, failure)**
Append to `tests/automation/test_dispatcher.cpp`:
```cpp
TEST_CASE("file.open accepts a bare string path", "[automation][rpc]") {
MockUiBackend mock;
mock.open_return_count = 1;
JsonRpcDispatcher d(mock);
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",2},{"method","file.open"},
{"params",{{"paths","C:/abs/a.stl"}}}});
CHECK(resp.at("result").at("loaded") == 1);
REQUIRE(mock.opened_paths.size() == 1);
REQUIRE(mock.opened_paths[0].size() == 1);
CHECK(mock.opened_paths[0][0] == "C:/abs/a.stl");
}
TEST_CASE("file.open with missing paths -> invalid params", "[automation][rpc]") {
MockUiBackend mock;
JsonRpcDispatcher d(mock);
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",3},{"method","file.open"},
{"params", json::object()}});
CHECK(resp.at("error").at("code") == kInvalidParams);
CHECK(mock.opened_paths.empty());
}
TEST_CASE("file.open with empty paths array -> invalid params", "[automation][rpc]") {
MockUiBackend mock;
JsonRpcDispatcher d(mock);
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",4},{"method","file.open"},
{"params",{{"paths", json::array()}}}});
CHECK(resp.at("error").at("code") == kInvalidParams);
CHECK(mock.opened_paths.empty());
}
TEST_CASE("file.open with a non-string entry -> invalid params", "[automation][rpc]") {
MockUiBackend mock;
JsonRpcDispatcher d(mock);
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",5},{"method","file.open"},
{"params",{{"paths", json::array({"C:/a.stl", 42})}}}});
CHECK(resp.at("error").at("code") == kInvalidParams);
CHECK(mock.opened_paths.empty());
}
TEST_CASE("file.open backend load failure -> 1007", "[automation][rpc]") {
MockUiBackend mock;
mock.open_should_fail = true;
JsonRpcDispatcher d(mock);
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",6},{"method","file.open"},
{"params",{{"paths","C:/abs/a.stl"}}}});
CHECK(resp.at("error").at("code") == kErrLoadFailed);
}
```
- [ ] **Step 8: Run all file.open tests to verify they pass**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests && build/tests/automation/RelWithDebInfo/automation_tests.exe "file.open*"`
Expected: PASS — 6 test cases, all assertions passed.
- [ ] **Step 9: Commit**
```bash
git add src/slic3r/GUI/Automation/JsonRpcDispatcher.hpp src/slic3r/GUI/Automation/JsonRpcDispatcher.cpp tests/automation/test_dispatcher.cpp
git commit -m "feat(automation): add file.open dispatcher handler with validation + tests"
```
---
## Task 3: Advertise `file.open` in `automation.version` capabilities
**Files:**
- Modify: `src/slic3r/GUI/Automation/JsonRpcDispatcher.cpp:166-172`
- Test: `tests/automation/test_dispatcher.cpp`
- [ ] **Step 1: Write the failing capabilities test**
Append to `tests/automation/test_dispatcher.cpp`:
```cpp
TEST_CASE("automation.version capabilities include file.open", "[automation][rpc]") {
MockUiBackend mock;
JsonRpcDispatcher d(mock);
const json resp = d.dispatch({{"jsonrpc","2.0"},{"id",1},{"method","automation.version"}});
const auto& caps = resp.at("result").at("capabilities");
bool found = false;
for (const auto& c : caps) if (c == "file.open") found = true;
CHECK(found);
}
```
- [ ] **Step 2: Run the test to verify it fails**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests && build/tests/automation/RelWithDebInfo/automation_tests.exe "automation.version capabilities include file.open"`
Expected: FAIL — `CHECK(found)` is false; `file.open` is not yet in the capabilities array.
- [ ] **Step 3: Add `file.open` to the capabilities array**
In `JsonRpcDispatcher::m_version` (currently lines 166-172), add `"file.open"` to the array:
```cpp
nlohmann::json JsonRpcDispatcher::m_version(const nlohmann::json&) {
return { {"version", kAutomationVersion},
{"protocol", "2.0"},
{"capabilities", nlohmann::json::array({
"tree.dump","tree.find","widget.get","input.click","input.type",
"input.key","sync.wait_for","app.state","screenshot.window",
"file.open" })} };
}
```
- [ ] **Step 4: Run the test to verify it passes**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests && build/tests/automation/RelWithDebInfo/automation_tests.exe "automation.version capabilities include file.open"`
Expected: PASS.
- [ ] **Step 5: Run the whole automation suite to confirm no regressions**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests && build/tests/automation/RelWithDebInfo/automation_tests.exe --order rand --warn NoAssertions`
Expected: PASS — all cases green (the pre-existing ~32 plus the 7 new `file.open`/capabilities cases ≈ 39).
- [ ] **Step 6: Commit**
```bash
git add src/slic3r/GUI/Automation/JsonRpcDispatcher.cpp tests/automation/test_dispatcher.cpp
git commit -m "feat(automation): advertise file.open in automation.version capabilities"
```
---
## Task 4: Implement `WxUiBackend::open_files` (real GUI-thread load)
Not covered by the headless unit suite (`WxUiBackend.cpp` is excluded from `automation_tests`); verified by the full app build + the manual runtime check in Task 8.
**Files:**
- Modify: `src/slic3r/GUI/Automation/WxUiBackend.hpp:21`
- Modify: `src/slic3r/GUI/Automation/WxUiBackend.cpp`
- [ ] **Step 1: Declare the override**
In `src/slic3r/GUI/Automation/WxUiBackend.hpp`, after the `screenshot_window` declaration (line 21) add:
```cpp
PngImage screenshot_window(const UiNode* target) override;
int open_files(const std::vector<std::string>& paths) override;
```
- [ ] **Step 2: Implement `open_files`**
In `src/slic3r/GUI/Automation/WxUiBackend.cpp`, add the implementation just before the final `}}} // namespace Slic3r::GUI::Automation` (currently line 306):
```cpp
int WxUiBackend::open_files(const std::vector<std::string>& paths) {
return run_on_gui(m_gui_timeout_ms, [&]() -> int {
Plater* plater = wxGetApp().plater();
if (plater == nullptr)
throw AutomationError(kErrLoadFailed, "no plater to load into");
// Default strategy matches drag-drop / Plater::load_files's own default: it
// routes .3mf as a project and meshes as models based on file content, so no
// as_project flag is needed in v1. ask_multi=false: never prompt.
const LoadStrategy strategy = LoadStrategy::LoadModel | LoadStrategy::LoadConfig;
std::vector<size_t> loaded;
try {
loaded = plater->load_files(paths, strategy, /*ask_multi=*/false);
} catch (const std::exception& e) {
throw AutomationError(kErrLoadFailed,
std::string("load_files failed: ") + e.what());
}
if (loaded.empty())
throw AutomationError(kErrLoadFailed, "load_files loaded nothing");
return static_cast<int>(loaded.size());
});
}
```
Notes for the implementer:
- `LoadStrategy` and its `operator|` (namespace `Slic3r`, from `libslic3r/Format/bbs_3mf.hpp`) are already in scope: `WxUiBackend.cpp` includes `Plater.hpp` (line 7), which transitively pulls in the enum, and this translation unit lives in `Slic3r::GUI::Automation` so unqualified `LoadStrategy` resolves via the enclosing `Slic3r` namespace. No new include is required.
- `Plater::load_files(const std::vector<std::string>&, LoadStrategy, bool)` is the existing string overload (`Plater.hpp:379`) — no `boost::filesystem::path` conversion needed.
- `kErrLoadFailed` comes from `JsonRpcDispatcher.hpp`, already included at line 4.
- An `AutomationError` thrown inside the `run_on_gui` lambda is captured by the helper's `set_exception` and rethrown from `fut.get()`, so the 1007 code propagates to the dispatcher unchanged.
- [ ] **Step 3: Build the full app to verify it compiles and links**
Run: `cmake --build build --config RelWithDebInfo --target OrcaSlicer`
Expected: build succeeds (no missing-symbol / pure-virtual errors; `WxUiBackend` is now concrete).
- [ ] **Step 4: Commit**
```bash
git add src/slic3r/GUI/Automation/WxUiBackend.hpp src/slic3r/GUI/Automation/WxUiBackend.cpp
git commit -m "feat(automation): implement WxUiBackend::open_files via Plater::load_files"
```
---
## Task 5: Python client wrapper `OrcaClient.open`
**Files:**
- Modify: `tools/automation/orca_automation.py:80-82`
- [ ] **Step 1: Add the `open` method**
In `tools/automation/orca_automation.py`, after the `key` method (ends line 82), add:
```python
def key(self, keys) -> dict:
# keys: "ctrl+s" or ["ctrl", "s"]
return self._call("input.key", {"keys": keys})
def open(self, paths) -> dict:
"""Load one or more files into the running instance at runtime.
`paths` is a single absolute path string or a list of them. Paths are read
from the host filesystem by the server (localhost-only). Returns
{"ok": True, "loaded": <count>}. Raises OrcaError 1007 on load failure."""
if isinstance(paths, str):
paths = [paths]
return self._call("file.open", {"paths": list(paths)})
```
- [ ] **Step 2: Smoke-test the wrapper's normalization offline (no server needed)**
Run:
```bash
python -c "import sys; sys.path.insert(0, 'tools/automation'); import orca_automation as m; c = m.OrcaClient.__new__(m.OrcaClient); c._call = lambda meth, params=None: (meth, params); print(c.open('C:/a.stl')); print(c.open(['C:/a.stl','C:/b.stl']))"
```
Expected output:
```
('file.open', {'paths': ['C:/a.stl']})
('file.open', {'paths': ['C:/a.stl', 'C:/b.stl']})
```
- [ ] **Step 3: Commit**
```bash
git add tools/automation/orca_automation.py
git commit -m "feat(automation): add OrcaClient.open() wrapper for file.open"
```
---
## Task 6: Update `example_slice.py` to load at runtime via `file.open`
**Files:**
- Modify: `tools/automation/example_slice.py:26-52`
- [ ] **Step 1: Launch without the model arg, then call `open`**
In `tools/automation/example_slice.py`, change the `subprocess.Popen` call (lines 26-31) to drop the trailing model positional:
```python
proc = subprocess.Popen([
args.orca,
"--automation-server",
f"--automation-server-port={args.port}",
])
```
Then replace the project-load wait block (currently lines 46-51) so the model is loaded at runtime via `file.open` instead of relying on a launch-time positional:
```python
# Load the model into the already-running instance, then wait until the
# project reports loaded. file.open is synchronous, so project_loaded is
# already true on return; the wait is a belt-and-suspenders guard.
orca.open([args.model])
deadline = time.time() + 30
while time.time() < deadline:
if orca.app_state().get("project_loaded"):
break
time.sleep(0.5)
```
- [ ] **Step 2: Byte-compile the script to confirm no syntax errors**
Run: `python -m py_compile tools/automation/example_slice.py`
Expected: no output, exit code 0.
- [ ] **Step 3: Commit**
```bash
git add tools/automation/example_slice.py
git commit -m "docs(automation): example_slice.py loads model at runtime via file.open"
```
---
## Task 7: Document `file.open` in `doc/automation.md`
**Files:**
- Modify: `doc/automation.md` (capabilities example §4 line 111-114; new method subsection after `screenshot.window`; error table §7)
- [ ] **Step 1: Add `file.open` to the capabilities example**
In `doc/automation.md`, update the `automation.version` result example (lines 111-114) to include `file.open`:
```json
"capabilities": [
"tree.dump", "tree.find", "widget.get", "input.click", "input.type",
"input.key", "sync.wait_for", "app.state", "screenshot.window", "file.open"
]
```
The §4 prose count is already written for this: "There are 11 methods … the 10 callable feature methods" now matches exactly (10 capability entries + `automation.version` = 11). Leave that sentence unchanged.
- [ ] **Step 2: Add the `file.open` method subsection**
In `doc/automation.md`, immediately after the `screenshot.window` method subsection (it ends just before the `---` on line 303) and before that `---`, insert:
```markdown
### `file.open`
Load one or more files into the **already-running** instance at runtime, by calling
`Plater::load_files(...)` directly on the GUI thread. This is the supported way to add
or swap a model without relaunching the process. Loading is **synchronous**: when the
call returns `ok: true`, `app.state().project_loaded` is already `true` (no polling
race).
**Params:**
| Param | Type | Required | Meaning |
|---|---|---|---|
| `paths` | string or array of strings | yes | One or more **absolute** file paths. A bare string is accepted and treated as a one-element list. Paths are read from the **host (server) filesystem** — client and server are localhost-only. |
`.3mf` files are routed as projects and meshes as models automatically, based on file
content (the same default strategy as drag-drop); there is no `as_project` flag in v1.
**Result:** `{ "ok": true, "loaded": <int> }`, where `loaded` is the number of objects
added to the scene (`load_files(...).size()`).
**Errors:**
- `-32602` (invalid params) — `paths` is missing, is not a string/array, contains a
non-string entry, or yields no non-empty path.
- `1007` (load failed) — `load_files` returned empty or threw (file not found, parse
error, or unsupported format).
- `1004` (GUI busy) — the GUI-thread marshal timed out. An extremely large model can
exceed the marshal timeout and surface here; documented, not mitigated in v1.
```
- [ ] **Step 3: Add the `1007` row to the error-code table**
In `doc/automation.md` §7, in the application-specific codes table, after the `1006` row (line 395) add:
```markdown
| `1006` | Disabled. |
| `1007` | Load failed — `file.open`'s `load_files` returned empty or threw (not found, parse error, unsupported format). |
```
- [ ] **Step 4: Commit**
```bash
git add doc/automation.md
git commit -m "docs(automation): document file.open method and error 1007"
```
---
## Final verification
- [ ] **Step 1: Full automation unit suite green**
Run: `cmake --build build --config RelWithDebInfo --target automation_tests && build/tests/automation/RelWithDebInfo/automation_tests.exe --order rand --warn NoAssertions`
Expected: PASS — all cases (pre-existing ~32 + 7 new) green, no `NoAssertions` warnings on the new cases.
- [ ] **Step 2: Full app builds**
Run: `cmake --build build --config RelWithDebInfo --target ALL_BUILD -- -m`
Expected: build succeeds.
- [ ] **Step 3: Manual runtime check (requires a display)**
Launch with `--automation-server` and **no** model arg, then from a Python shell:
```python
from orca_automation import OrcaClient
orca = OrcaClient(port=13619)
print(orca.open(["C:/abs/path/cube.stl"])) # -> {'ok': True, 'loaded': 1}
print(orca.app_state()["project_loaded"]) # -> True
open("window.png","wb").write(orca.screenshot()) # PNG shows the loaded model
```
Expected: `loaded >= 1`, `project_loaded == True`, screenshot shows the model.
- [ ] **Step 4: Gating check (automation OFF is a no-op)**
Confirm by reading: with no `--automation-server` flag, the server/backend/dispatcher are never constructed (`GUI_App.cpp` `start_automation_server()` early-return), so `file.open` is unreachable. No new hot-path cost beyond the existing single bool check. (See `doc/automation.md` §Verification — disabled-path audit; this feature adds no new gating surface.)
---
## Backward compatibility
Additive only: one new method (`file.open`), one new error code (`1007`), one new capabilities entry, and one new backend interface method. No existing method, profile, project-file handling, or default behavior changes. The method is reachable only when `--automation-server` is passed.

File diff suppressed because it is too large Load Diff

View File

@@ -1,117 +0,0 @@
# Design — `file.open` automation method (runtime model loading)
**Date:** 2026-06-03
**Branch:** `feature/automation`
**Status:** Approved for implementation
---
## Problem
Today a model can be loaded into OrcaSlicer **only at process launch**: the model path
is passed as a CLI positional arg and OrcaSlicer's normal startup file-loading ingests
it. The JSON-RPC automation protocol has **no** load method, so swapping or adding a
model in an already-running instance requires a fresh process launch.
Driving the native File→Import dialog via `input.click` is not a viable substitute: the
OS file picker is not a `wxWindow`, so it never appears in the `tree.dump` hierarchy
(`WxUiBackend::dump_tree` walks `wxGetApp().mainframe` children only), and `input.click`
can only target nodes resolved from that tree (no raw-coordinate click). Blind typing via
`input.type` is mechanically possible but unobservable: the native picker is not a
`wxDialog`, so `app.state().modal_dialog` and `sync.wait_for` cannot gate on it, leaving
only sleep-and-hope timing. A direct API method is the clean fix.
## Goal
Add a `file.open` JSON-RPC method that loads one or more files into a running instance by
calling `Plater::load_files(...)` directly on the GUI thread. Out of scope: any
dialog-driving mechanism (intercept hook or true OS-level drive) — explicitly deferred.
---
## Protocol
- **Method:** `file.open`
- **Params:** `{ "paths": ["C:/abs/a.stl", ...] }`
- A bare string is also accepted: `{ "paths": "C:/abs/a.stl" }`.
- Paths must be **absolute**. The server reads them from the host filesystem
(client/server are localhost-only).
- **Result:** `{ "ok": true, "loaded": <count> }`
- `count` is `load_files(...).size()` — the number of objects added to the scene.
- **Errors:**
| Code | Constant | Condition |
|------|----------|-----------|
| -32602 | `kInvalidParams` | `paths` missing/empty, a non-string entry, or no non-empty path |
| 1004 | `kErrGuiBusy` | GUI-thread marshal timed out (`m_gui_timeout_ms`) |
| 1007 | `kErrLoadFailed` | `load_files` returned empty / threw (not found, parse error, unsupported format) — **new code** |
## Semantics — synchronous
`Plater::load_files` runs and completes on the GUI thread. The backend marshals via the
existing `run_on_gui(m_gui_timeout_ms, …)` helper and returns only after the load
finishes. Consequently, when `file.open` returns `ok:true`, `app.state().project_loaded`
is already `true` — there is no polling race.
Rejected alternative — async "fire-and-poll-`project_loaded`": adds client complexity and
loses a definitive per-call error result, with no benefit since loading is synchronous.
**Caveat:** an extremely large model could exceed `m_gui_timeout_ms` and surface as
`1004 kErrGuiBusy`. Documented; not mitigated in v1.
## Load strategy (v1 minimal)
Pass the default `LoadStrategy::LoadModel | LoadStrategy::LoadConfig` (identical to
drag-drop / `Plater::load_files`'s default) with `ask_multi = false`. This already routes
`.3mf` files as projects and meshes as models based on file content, so **no `as_project`
flag is needed in v1**. A future `{ "as_project": bool }` flag remains possible but is not
implemented now.
---
## Components / files to touch
Follows the existing `screenshot_window` / `app_state` method pattern.
1. **`src/slic3r/GUI/Automation/IUiBackend.hpp`** — add pure-virtual
`int open_files(const std::vector<std::string>& paths)` returning the loaded count,
throwing `AutomationError` on failure. Header stays wx-free (no `LoadStrategy` leak).
2. **`src/slic3r/GUI/Automation/WxUiBackend.{hpp,cpp}`** — implement `open_files`:
`run_on_gui(m_gui_timeout_ms, …)``wxGetApp().plater()->load_files(paths, default_strategy, false)`;
throw `kErrLoadFailed` if the returned vector is empty.
3. **`src/slic3r/GUI/Automation/JsonRpcDispatcher.{hpp,cpp}`** —
- add `constexpr int kErrLoadFailed = 1007;`
- declare + define `m_file_open(params)` (param parsing/validation; accept string or
array; require ≥1 non-empty string path)
- add dispatch route `if (method == "file.open") return make_result(id, m_file_open(params));`
- add `"file.open"` to the capabilities array in `m_version`.
4. **`tests/automation/MockUiBackend.hpp`** — `open_files` override recording the paths
vector + a configurable return-count (and a throw/fail knob).
5. **`tests/automation/test_dispatcher.cpp`** — Catch2 v2 tests:
- array of paths → routes to backend, returns `loaded` count
- bare-string path → normalized to one path
- missing/empty `paths``-32602` (`kInvalidParams`)
- backend load failure → `1007`
- `automation.version` capabilities array includes `"file.open"`
6. **`tools/automation/orca_automation.py`** — `open(self, paths)` wrapper (normalize
`str``[str]`, send `file.open`).
7. **`tools/automation/example_slice.py`** — launch **without** a model arg, then
`orca.open([model])`, then wait for `project_loaded`.
8. **`doc/automation.md`** — document method (params/result/errors), add to the
capabilities list, method index, and error table (`1007`).
---
## Testing / verification
- **Build (Windows):** `cmake --build . --config RelWithDebInfo --target ALL_BUILD -- -m`.
- **Unit:** `automation` Catch2 suite green including new tests (≈31 → ≈34 cases).
- **Manual:** launch with `--automation-server` and **no** model arg → call `file.open`
→ confirm `app.state().project_loaded` flips `true` and `screenshot.window` shows the
model.
- **Gating:** unchanged — the server only runs under `--automation-server`, so the method
is a no-op (unreachable) when automation is disabled.
## Backward compatibility
Additive only: a new method, a new error code, and a new capabilities entry. No change to
existing methods, profiles, project-file handling, or default behavior.

View File

@@ -1,314 +0,0 @@
# OrcaSlicer UI Automation — Design Spec
**Date:** 2026-06-03
**Status:** Approved design, pending implementation plan
**Topic:** Add an opt-in, externally-controllable UI automation interface to OrcaSlicer for automated GUI testing and future AI-agent control.
---
## 1. Overview
Add a localhost JSON-RPC server to a running OrcaSlicer GUI instance that lets an
**external** script (or AI agent) drive and observe the real GUI. "Driving" is done
the way a user would — simulated mouse/keyboard via `wxUIActionSimulator` — while
"observing" reads the live widget state and captures screenshots.
The interface must cover **three UI technologies** present in OrcaSlicer:
1. Native **wxWidgets** widgets (`wxWindow` hierarchy).
2. The **OpenGL 3D viewport** (`GLCanvas3D`) — screenshots via the existing
framebuffer/thumbnail path.
3. **Dear ImGui** immediate-mode controls (gizmo panels, in-canvas overlays,
notifications) — recorded as they are drawn, because there is no persistent tree.
## 2. Goals / Non-Goals
### Goals
- Let an external, language-agnostic client connect to a running instance and:
introspect the UI, locate widgets by stable name, perform input actions, wait on
conditions, and capture screenshots (including the 3D view as a separate image).
- Be safe by default: disabled unless explicitly enabled; bound to `127.0.0.1` only.
- Be testable in CI without a display (pure-logic units behind a mock backend).
- Ship a reference Python client, a runnable end-to-end example, protocol docs, and
C++ unit tests.
### Non-Goals (v1)
- No headless/offscreen automation — OS input injection needs a focused, visible
window (Linux CI requires a display, e.g. Xvfb).
- No auth token in v1 (documented future hardening; localhost-only is the boundary).
- No per-item coverage of raw-`ImGui::` gizmos (Emboss/SVG/Text). They get
window-level coverage; per-item is future work.
- No new scripting language embedded in the app; control is purely external over JSON-RPC.
- We do **not** modify the existing auth `HttpServer`.
## 3. Background — existing infrastructure (verified)
- **`wxUIActionSimulator`** is compiled into the wx build and already used
(`src/slic3r/GUI/GUI_ObjectList.cpp:211`). Cross-platform simulated input is available.
- **`GLCanvas3D::render_thumbnail()`** (`src/slic3r/GUI/GLCanvas3D.cpp:2210+`) →
`render_thumbnail_framebuffer()` (`:6352`) renders the 3D scene into a
`ThumbnailData` (RGBA) via an FBO + `glReadPixels`. `debug_output_thumbnail()`
(`:6099`) shows the `ThumbnailData → wxImage → PNG` conversion. This is the
separate-3D-screenshot path. `Plater::generate_thumbnail()`/`generate_thumbnails()`
wrap it.
- **`ImGuiWrapper`** (`src/slic3r/GUI/ImGuiWrapper.hpp`) is the chokepoint for nearly
all ImGui controls: `button`/`bbl_button`, `checkbox`/`bbl_checkbox`, `combo`,
`slider_float`, `input_double`, `radio_button`, `menu_item_with_icon`, plus
`begin`/`end` for windows. `imgui_internal.h` is in-tree, exposing
`ImGui::GetCurrentContext()->Windows`, item rects, and hovered/active id.
- **`HttpServer`** (`src/slic3r/GUI/HttpServer.{hpp,cpp}`, boost::beast, port 13618)
is used for cloud auth. **It cannot serve a POST body**`session::read_body()`
reads and discards the body and never replies (`HttpServer.cpp:57-65`). It is
effectively GET-only. We will **not** reuse or modify it.
- **`OtherInstanceMessageHandler`** (`src/slic3r/GUI/InstanceCheck.{hpp,cpp}`) is the
template for "start a localhost listener once the MainFrame exists, post events into
the GUI." Useful as a structural reference.
- **CLI args** are parsed in `src/OrcaSlicer.cpp` (`CLI::setup`/`CLI::run`) and flow
into the GUI run params consumed by `GUI_App::OnInit()`.
## 4. Architecture
```
External script / AI agent ──► Python client (orca_automation.py)
│ HTTP POST /jsonrpc (JSON-RPC 2.0) on 127.0.0.1:<port>
AutomationServer (dedicated boost::beast listener; own thread;
│ started only when --automation-server is set)
│ parse JSON-RPC envelope
JsonRpcDispatcher (pure logic — method registry; unit-testable)
│ marshal each call to the GUI thread via wxGetApp().CallAfter
│ + std::promise/future with a per-request timeout
IUiBackend (interface) ──► WxUiBackend (real, GUI thread) / MockBackend (tests)
├─ Introspection : walk wxWindow tree + read ImGui item table → unified JSON
├─ Locator : resolve automation-id / predicate → wx widget or ImGui item
├─ Actions : raise window, then wxUIActionSimulator click/type/key
├─ Sync : wait_for (poll condition) + app.state snapshot
└─ Screenshots : wx widget → wxDC→PNG ; 3D view → render_thumbnail()→PNG
```
### Components (new files unless noted)
All new code lives under `src/slic3r/GUI/Automation/`.
| Component | Responsibility |
|---|---|
| `AutomationServer.{hpp,cpp}` | Dedicated boost::beast HTTP listener with **POST + body** support; one `POST /jsonrpc` endpoint; returns `application/json`. Localhost-only. Own thread. |
| `JsonRpcDispatcher.{hpp,cpp}` | Parse JSON-RPC 2.0; route `method` → handler; build result/error. Depends only on `IUiBackend`. No wx/ImGui includes → unit-testable. |
| `IUiBackend.hpp` | Abstract interface: `dump_tree`, `find`, `get_widget`, `click`, `type`, `key`, `wait_for`, `app_state`, `screenshot_window`, `screenshot_viewport3d`. Uses plain structs (no wx types) so tests can mock it. |
| `WxUiBackend.{hpp,cpp}` | Real implementation. Runs on GUI thread. Walks `wxWindow` tree, reads the ImGui item table, drives `wxUIActionSimulator`, captures screenshots. |
| `MockUiBackend.{hpp,cpp}` (tests) | Deterministic fake tree + recorded actions for unit tests. |
| `AutomationRegistry.{hpp,cpp}` | Process-wide `wxWindow* → automation_id` map + reverse lookup; `set_automation_id(win, "id")` helper. Header is dependency-light so widget-construction code can call the helper unconditionally (it is a cheap no-op-safe registration). |
| `WidgetSerializer.{hpp,cpp}` | `wxWindow` → JSON node (name/id, class, label, screen-rect, enabled, shown, value via RTTI). |
| `ImGuiItemTable.{hpp,cpp}` | Per-frame recorder of ImGui items + live-window enumeration. Populated from `ImGuiWrapper`; read on GUI thread. |
### Touch points in existing files
- `src/slic3r/GUI/ImGuiWrapper.cpp` — add recording hooks inside the wrapped widget
methods and `begin`/`end`, **guarded by an `is_automation_enabled()` flag** so there
is zero overhead and zero behavior change when automation is off.
- `src/slic3r/GUI/GUI_App.{hpp,cpp}` — own the `AutomationServer`; start it in
`OnInit()` only when the flag is set; stop it on exit. Expose
`is_automation_enabled()`.
- `src/OrcaSlicer.cpp` — parse `--automation-server[=PORT]`; pass through GUI run params.
- A handful of widget-construction sites (Slice/Export buttons, preset combos, main
tabs, common dialog OK/Cancel, the 3D canvas) — add `set_automation_id(...)` calls
(~15-20 widgets in v1).
- CMake: add the new `Automation/` sources to the GUI target; add the unit-test target.
## 5. Transport & Protocol
- **Transport:** HTTP/1.1 on `127.0.0.1:<port>` (default **13619**, adjacent to the
auth server's 13618). Single endpoint: `POST /jsonrpc`, body is a JSON-RPC 2.0
request, response is a JSON-RPC 2.0 result/error. `GET /` returns a tiny health/version page.
- **Protocol:** JSON-RPC 2.0. `id`, `method`, `params`. Batch not required in v1.
### v1 methods
| Method | Params | Result |
|---|---|---|
| `automation.version` | — | `{version, protocol, capabilities[]}` |
| `tree.dump` | `{root?, max_depth?, visible_only?, include_imgui?}` | tree of nodes (wx + imgui) |
| `tree.find` | `{name?, class?, label?, value?, backend?}` | `[node...]` matches |
| `widget.get` | `{target}` | single node detail |
| `input.click` | `{target, button?=left, double?=false, modifiers?[]}` | `{ok}` |
| `input.type` | `{target?, text}` | `{ok}` |
| `input.key` | `{keys}` e.g. `"ctrl+s"` or `["ctrl","s"]` | `{ok}` |
| `sync.wait_for` | `{target, state: exists\|visible\|enabled\|value, value?, timeout_ms?=5000, poll_ms?=100}` | `{ok, elapsed_ms}` |
| `app.state` | — | `{active_tab, project_loaded, slicing, slice_progress, modal_dialog?, foreground}` |
| `screenshot.window` | `{target?}` (default main frame) | `{png_base64, width, height}` |
| `screenshot.viewport3d` | `{plate?, width?, height?}` | `{png_base64, width, height}` |
### Node shape (unified for wx and ImGui)
```json
{
"backend": "wx" | "imgui",
"id": "btn_slice", // automation id if set, else derived path id
"path": "MainFrame/.../btn_slice", // stable-ish positional path
"class": "Button", // wx class name or imgui item type
"label": "Slice plate",
"rect": { "x": 100, "y": 200, "w": 120, "h": 32 }, // screen coords
"enabled": true,
"visible": true,
"value": "PLA", // when applicable (text/choice/check/slider)
"children": [ ... ] // wx only; imgui items are flat under their window
}
```
### Error model (JSON-RPC `error.code`)
- `-32700` parse error, `-32601` method not found, `-32602` invalid params (standard).
- Application codes: `1001` widget/target not found, `1002` target not actionable
(disabled/hidden), `1003` wait timeout, `1004` GUI thread busy/timeout,
`1005` screenshot failed, `1006` automation feature disabled.
## 6. Threading model
- `AutomationServer` runs on its own thread and accepts connections; the dispatcher
parses on that thread.
- **Every** call touching wx/ImGui/GL is marshaled to the GUI thread with
`wxGetApp().CallAfter([...]{ ... })`; the server thread blocks on a `std::future`
with a per-request timeout (default 5 s; `wait_for` uses its own larger budget).
Timeout → error `1004`.
- This is mandatory: wx widgets, the ImGui context, and the GL context are not
thread-safe and are owned by the GUI thread.
- `CallAfter` is serviced even while modal dialogs run (nested event loop), so
automation can interact with dialogs.
## 7. Widget locator & automation IDs (wxWidgets)
- **Stable IDs:** `set_automation_id(window, "btn_slice")` registers the widget in
`AutomationRegistry`. Stored in a side map keyed by `wxWindow*` (not via `SetName`,
to avoid any coupling with wx's name-based lookups). Registration is removed on
widget destruction (bind to `wxEVT_DESTROY` or prune lazily on lookup).
- **Derived IDs:** for un-instrumented widgets, `WidgetSerializer` derives a positional
`path` (e.g. `MainFrame/Panel[2]/Button[0]`) so an AI agent can still target anything.
Named IDs are the preferred, stable path.
- **Locator resolution order:** exact automation id → exact path → predicate match
(name/class/label/value). Ambiguous matches return the list via `tree.find`; action
methods require a unique match or error `1001`.
- **v1 instrumented widgets (~15-20):** Slice/Export buttons, printer & filament preset
combos, the main tab buttons (`tp3DEditor`/`tpPreview`/`tpMonitor`/…), Add/Import,
common dialog OK/Cancel/Yes/No, the `GLCanvas3D` itself.
## 8. ImGui coverage (v1 = wrapper items + window introspection)
- **Item recording:** inside each `ImGuiWrapper` wrapped method, when
`is_automation_enabled()` is true, append the just-drawn item to a per-frame
`ImGuiItemTable` entry: `{window_name, label/id, type, rect, enabled, value}`.
Item rect comes from `ImGui::GetItemRectMin/Max()` (ImGui display coords) mapped to
**screen** coords via the `GLCanvas3D` client origin (`ClientToScreen`) and DPI scale.
- **Window enumeration:** via `imgui_internal.h`, enumerate `GetCurrentContext()->Windows`
for window name, rect, visibility, plus the global hovered/active item id.
- **Double-buffering:** the table is swapped at frame end (`ImGuiWrapper::render`) so
readers see a complete frame. Reads happen on the GUI thread (after marshaling), same
thread as rendering, so a simple front/back swap suffices.
- **Freshness:** because items exist only while drawn, before an ImGui tree read or
action the backend forces a canvas refresh and flushes events so the latest frame is
captured. `sync.wait_for` can poll for an ImGui item to appear (e.g. after opening a
gizmo).
- **Actions:** an ImGui target resolves to its recorded screen rect; `input.click`/
`input.type` use `wxUIActionSimulator` on that rect — identical action path to wx,
different rect source. Typing into an ImGui input works because simulated keystrokes
flow through the existing `ImGuiWrapper::update_key_data` bridge once the field is
focused by a click.
- **Limitation (documented):** raw-`ImGui::` gizmos (Emboss, SVG, Text) are covered at
the **window** level only in v1; per-item instrumentation is future work.
## 9. Screenshots
- **`screenshot.window`:** capture a `wxWindow` (default: main frame) via
`wxClientDC`/`wxWindowDC``wxBitmap``wxImage` → PNG → base64. Works for native
widgets but **not** for the GL canvas region (returns black there) — hence the
separate 3D method.
- **`screenshot.viewport3d`:** reuse `GLCanvas3D::render_thumbnail()` (FBO +
`glReadPixels`) → `ThumbnailData``wxImage` (per `debug_output_thumbnail`) → PNG →
base64. Optional `plate`, `width`, `height` params. Runs on the GUI thread with the GL
context current.
## 10. Activation & security
- **Off by default.** Enabled by CLI flag `--automation-server[=PORT]` (default port
13619). (An app-config/Preferences toggle may be added later; v1 is flag-only.)
- **Bind `127.0.0.1` only.** No external interface.
- **No token in v1** (per decision); documented as a recommended future hardening,
along with an optional `--automation-token`.
- When disabled: no listener, no thread, and all `ImGuiWrapper` recording hooks are
skipped — **zero** runtime overhead and **zero** behavior change. This satisfies the
project's "features gated by options must not affect existing behavior when disabled"
constraint.
## 11. Testability
- `JsonRpcDispatcher` depends only on `IUiBackend` and has **no** wx/ImGui/GL includes.
- **C++ unit tests (Catch2), display-free, run in CI:**
- JSON-RPC envelope parse/validate/dispatch (good + malformed input, error codes).
- Method routing and param validation for every v1 method against `MockUiBackend`.
- `WidgetSerializer` node shape (fed a synthetic node model, not real wx widgets).
- Locator resolution: exact id, path, predicate, ambiguity, not-found.
- The only piece needing a real GUI is `WxUiBackend`; it is exercised by the manual
end-to-end example, not by CI unit tests.
## 12. Deliverables
- **C++:** the `Automation/` components, `ImGuiWrapper` recording hooks, widget
instrumentation, CLI flag plumbing, `GUI_App` lifecycle, CMake wiring.
- **`tools/automation/orca_automation.py`:** reference Python client wrapping the
JSON-RPC calls (`connect`, `version`, `dump_tree`, `find`, `click`, `type`, `key`,
`wait_for`, `app_state`, `screenshot`, `screenshot_3d`).
- **`tools/automation/example_slice.py`:** runnable end-to-end flow — launch OrcaSlicer
with the flag, load a model, click Slice, `wait_for` completion, save a 3D-preview PNG.
Doubles as a manual smoke test.
- **`doc/automation.md`:** protocol reference (methods, params, results, error codes),
node shape, automation-id naming conventions, ImGui notes, platform/display caveats.
- **`tests/`:** Catch2 unit-test target for the dispatch/serialize/locator logic.
## 13. New / changed file inventory
**New**
- `src/slic3r/GUI/Automation/AutomationServer.{hpp,cpp}`
- `src/slic3r/GUI/Automation/JsonRpcDispatcher.{hpp,cpp}`
- `src/slic3r/GUI/Automation/IUiBackend.hpp`
- `src/slic3r/GUI/Automation/WxUiBackend.{hpp,cpp}`
- `src/slic3r/GUI/Automation/AutomationRegistry.{hpp,cpp}`
- `src/slic3r/GUI/Automation/WidgetSerializer.{hpp,cpp}`
- `src/slic3r/GUI/Automation/ImGuiItemTable.{hpp,cpp}`
- `tools/automation/orca_automation.py`
- `tools/automation/example_slice.py`
- `doc/automation.md`
- `tests/automation/` (Catch2 target) + `MockUiBackend.{hpp,cpp}`
**Changed**
- `src/slic3r/GUI/ImGuiWrapper.cpp` (guarded recording hooks)
- `src/slic3r/GUI/GUI_App.{hpp,cpp}` (server lifecycle, `is_automation_enabled()`)
- `src/OrcaSlicer.cpp` (CLI flag)
- ~15-20 widget-construction sites (`set_automation_id`)
- `src/slic3r/GUI/CMakeLists.txt` + `tests/CMakeLists.txt`
## 14. Known constraints & limitations
- OS input injection requires the OrcaSlicer window **focused and visible**; the backend
raises/focuses the main window before injecting. Linux CI needs a display (Xvfb).
- Input is asynchronous at the OS level; correctness relies on `sync.wait_for` rather
than fixed sleeps.
- ImGui items are only addressable while their host panel is drawn.
- Raw-`ImGui::` gizmos: window-level only in v1.
- Single-client assumption in v1 (serialized request handling); no concurrent sessions
contract.
## 15. Future work (out of scope for v1)
- Optional auth token + Preferences toggle.
- WebSocket channel for server-push events (slice progress, dialog-appeared).
- Per-item instrumentation for raw-`ImGui::` gizmos.
- An MCP server wrapping the JSON-RPC client for direct AI-agent integration.
- Optional integration of Dear ImGui Test Engine for deterministic ImGui interaction.
## 16. Verification plan
- **CI:** Catch2 unit tests (dispatch/serialize/locator) pass with no display.
- **Manual / e2e:** run `tools/automation/example_slice.py` against a built OrcaSlicer
launched with `--automation-server`; confirm model loads, Slice runs, `wait_for`
returns on completion, and both a wx-window PNG and a 3D-viewport PNG are produced.
- **Regression:** build and run with automation **off**; confirm no new threads, no
listener, and ImGui rendering is byte-for-byte unchanged (hooks compiled out of the
hot path via the disabled flag).

View File

@@ -122,7 +122,7 @@ msgid "On highlighted overhangs only"
msgstr "Nur an hervorgehobenen Überhängen"
msgid "Erase all"
msgstr "Alles löschen"
msgstr ""
msgid "Highlight overhang areas"
msgstr "Bereiche mit Überhang hervorheben"
@@ -350,10 +350,10 @@ msgid "Fixed step drag"
msgstr "Fester Schritt ziehen"
msgid "Context Menu"
msgstr "Kontextmenü"
msgstr ""
msgid "Toggle Auto-Drop"
msgstr "Automatisches Absenken umschalten"
msgstr ""
msgid "Single sided scaling"
msgstr "Einseitige Skalierung"
@@ -509,10 +509,10 @@ msgid "Multiple"
msgstr "Mehrere"
msgid "Count"
msgstr "Anzahl"
msgstr ""
msgid "Gap"
msgstr "Spalt"
msgstr ""
msgid "Spacing"
msgstr "Abstand"
@@ -883,7 +883,7 @@ msgid "Advanced"
msgstr "Erweiterte Einstellungen"
msgid "Reset all options except the text and operation"
msgstr "Alle Optionen außer dem Text und der Operation zurücksetzen"
msgstr ""
msgid ""
"The text cannot be written using the selected font. Please try choosing a "
@@ -1805,16 +1805,16 @@ msgid "Info"
msgstr "Info"
msgid "Loading printer & filament profiles"
msgstr "Lade Drucker- und Filamentprofile"
msgstr ""
msgid "Creating main window"
msgstr "Erstelle Hauptfenster"
msgstr ""
msgid "Loading current preset"
msgstr "Lade aktuelles Preset"
msgstr ""
msgid "Showing main window"
msgstr "Zeige Hauptfenster"
msgstr ""
msgid ""
"The OrcaSlicer configuration file may be corrupted and cannot be parsed.\n"
@@ -1897,12 +1897,9 @@ msgid ""
"Please check your network connectivity\n"
"(HTTP %u)"
msgstr ""
"Verbindung zu OrcaCloud fehlgeschlagen.\n"
"Bitte überprüfen Sie Ihre Netzwerkverbindung\n"
"(HTTP %u)"
msgid "Cloud Error"
msgstr "Cloud-Fehler"
msgstr ""
msgid "Retrieving printer information, please try again later."
msgstr "Empfange Druckerinformationen, bitte später erneut versuchen."
@@ -6103,13 +6100,13 @@ msgid "Export"
msgstr "Exportieren"
msgid "Sync Presets"
msgstr "Presets synchronisieren"
msgstr ""
msgid "Pull and apply the latest presets from OrcaCloud"
msgstr "Die neuesten Presets von OrcaCloud abrufen und anwenden"
msgstr ""
msgid "You must be logged in to sync presets from cloud."
msgstr "Sie müssen angemeldet sein, um Presets aus der Cloud zu synchronisieren."
msgstr ""
msgid "Quit"
msgstr "Beenden"
@@ -6239,7 +6236,7 @@ msgid "Preset Bundle"
msgstr "Vorlagen-Bundle"
msgid "Syncing presets from cloud…"
msgstr "Synchronisiere Presets aus der Cloud…"
msgstr ""
msgid "Help"
msgstr "Hilfe"
@@ -8944,25 +8941,25 @@ msgid "If enabled, reverses the direction of zoom with mouse wheel."
msgstr "Wenn aktiviert, wird die Richtung des Zooms mit dem Mausrad umgekehrt."
msgid "Pan"
msgstr "Schwenken"
msgstr ""
msgid "Left Mouse Drag"
msgstr "Linke Maustaste drücken"
msgstr ""
msgid "Set the action that dragging the left mouse button should perform."
msgstr "Legen Sie die Aktion fest, die das Ziehen der linken Maustaste ausführen soll."
msgstr ""
msgid "Middle Mouse Drag"
msgstr "Mittlere Maustaste drücken"
msgstr ""
msgid "Set the action that dragging the middle mouse button should perform."
msgstr "Legen Sie die Aktion fest, die das Ziehen der mittleren Maustaste ausführen soll."
msgstr ""
msgid "Right Mouse Drag"
msgstr "Rechte Maustaste drücken"
msgstr ""
msgid "Set the action that dragging the right mouse button should perform."
msgstr "Legen Sie die Aktion fest, die das Ziehen der rechten Maustaste ausführen soll."
msgstr ""
msgid "Clear my choice on..."
msgstr "Meine Auswahl löschen bei ..."
@@ -8989,13 +8986,13 @@ msgstr ""
"der Datei."
msgid "Graphics"
msgstr "Grafik"
msgstr ""
msgid "Anti-aliasing"
msgstr "Kantenglättung"
msgstr ""
msgid "MSAA Multiplier"
msgstr "MSAA-Multiplikator"
msgstr ""
msgid ""
"Set the Multi-Sample Anti-Aliasing level.\n"
@@ -9007,19 +9004,12 @@ msgid ""
"\n"
"Requires application restart."
msgstr ""
"Stellen Sie die Stufe der Multi-Sample-Kantenglättung ein.\n"
"Höhere Werte führen zu glatteren Kanten, aber die Auswirkungen auf die "
"Leistung sind exponentiell.\n"
"Niedrigere Werte verbessern die Leistung auf Kosten von gezackten Kanten.\n"
"Wenn deaktiviert, wird empfohlen, FXAA zu aktivieren, um gezackte Kanten mit minimalen Auswirkungen auf die Leistung zu reduzieren.\n"
"\n"
"Erfordert einen Neustart der Anwendung."
msgid "Disabled"
msgstr "Deaktiviert"
msgid "FXAA post-processing"
msgstr "FXAA-Nachbearbeitung"
msgstr ""
msgid ""
"Applies Fast Approximate Anti-Aliasing as a screen-space pass.\n"
@@ -9027,34 +9017,26 @@ msgid ""
"\n"
"Takes effect immediately."
msgstr ""
"Führt Fast Approximate Anti-Aliasing als Bildschirmraum-Pass aus.\n"
"Nützlich, um die MSAA-Einstellung zu deaktivieren oder zu reduzieren, um die Leistung zu verbessern.\n"
"\n"
"ist sofort wirksam"
msgid "FPS"
msgstr "FPS"
msgstr ""
msgid "FPS cap"
msgstr "FPS-Begrenzung"
msgstr ""
msgid "(0 = unlimited)"
msgstr "(0 = unbegrenzt)"
msgstr ""
msgid ""
"Limits viewport frame rate to reduce GPU load and power usage.\n"
"Set to 0 for unlimited frame rate."
msgstr ""
"Begrenzt die Bildrate des Viewports, um die GPU-Auslastung und den "
"Energieverbrauch zu reduzieren.\n"
"Auf 0 setzen für unbegrenzte Bildrate."
msgid "Show FPS overlay"
msgstr "FPS-Overlay anzeigen"
msgstr ""
msgid "Displays current viewport FPS in the top-right corner."
msgstr "Zeigt die aktuelle FPS des Viewports in der oberen rechten Ecke an."
msgstr ""
msgid "Login region"
msgstr "Login region"
@@ -9225,17 +9207,13 @@ msgid "Skip AMS blacklist check"
msgstr "Überspringen der AMS Blacklist-Prüfung"
msgid "(Experimental) Keep painted feature after mesh change"
msgstr "(Experimentell) Behalte bemalte Funktionen nach Mesh-Änderung bei"
msgstr ""
msgid ""
"Attempt to keep painted features (color/seam/support/fuzzy etc.) after "
"changing the object mesh (such as cut/reload from disk/simplify/fix etc.)\n"
"Highly experimental! Slow and may create artifact."
msgstr ""
"Versuchen Sie, bemalte Funktionen (Farbe/Naht/Stütze/unscharf usw.) nach "
"Änderung des Objekt-Meshs (z. B. schneiden/neu laden von der Festplatte/vereinfachen/reparieren usw.) beizubehalten\n"
"Sehr experimentell! Langsam und kann Artefakte erzeugen."
msgid "Allow Abnormal Storage"
msgstr "Fehlerhaften Speicher zulassen"
@@ -10961,37 +10939,26 @@ msgid ""
" %s first layer %d %s, other layers %d %s\n"
" %s max delta %d %s, current delta %d %s\n"
msgstr ""
" - %s:\n"
" %s erste Schicht %d %s, andere Schichten %d %s\n"
" %s maximale Delta %d %s, aktuelle Delta %d %s\n"
msgid ""
"Some first-layer and other-layer temperature pairs exceed safety limits.\n"
msgstr "Einige Temperaturpaare für die erste und andere Schicht überschreiten die Sicherheitsgrenzen.\n"
msgstr ""
msgid ""
"\n"
"Invalid pairs:\n"
msgstr ""
"\n"
"Ungültige Paare:\n"
msgid ""
"\n"
"You can go back to edit values, or continue if this is intentional."
msgstr ""
"\n"
"Sie können zurückgehen, um die Werte zu bearbeiten, oder fortfahren, wenn dies "
"absichtlich ist."
msgid ""
"\n"
"\n"
"Continue anyway?"
msgstr ""
"\n"
"\n"
"Trotzdem fortfahren?"
msgid "Temperature Safety Check"
msgstr "Temperatur-Sicherheitsprüfung"
@@ -11075,10 +11042,6 @@ msgid ""
"\"%1%\"\n"
"and \"%2%\" will open without any changes."
msgstr ""
"Alle \"Neuer Wert\" Einstellungen in\n"
"\"%1%\"\n"
"werden gespeichert und \"%2%\" wird ohne Änderungen geöffnet."
msgid "Click the right mouse button to display the full text."
msgstr ""
@@ -11684,7 +11647,7 @@ msgid "Login"
msgstr "Anmelden"
msgid "Login failed. Please try again."
msgstr "Anmeldung fehlgeschlagen. Bitte versuchen Sie es erneut."
msgstr ""
msgid "[Action Required] "
msgstr "[Aktion erforderlich] "
@@ -11724,16 +11687,16 @@ msgid "Global shortcuts"
msgstr "Globale Tastaturkürzel"
msgid "Pan View"
msgstr "Ansicht verschieben"
msgstr ""
msgid "Rotate View"
msgstr "Ansicht drehen"
msgstr ""
msgid "Middle mouse button"
msgstr "Mittlere Maustaste"
msgstr ""
msgid "Zoom View"
msgstr "Ansicht zoomen"
msgstr ""
msgid ""
"Auto orients selected objects or all objects. If there are selected objects, "
@@ -12636,8 +12599,6 @@ msgid ""
"The Hollow base pattern is not supported by this support type; Rectilinear "
"will be used instead."
msgstr ""
"Das Hohl-Basis-Muster wird von diesem Stütztyp nicht unterstützt; Stattdessen "
"wird das Rechteckmuster verwendet."
msgid ""
"Support enforcers are used but support is not enabled. Please enable support."
@@ -14885,16 +14846,16 @@ msgid "Auto For Match"
msgstr "Automatisch für Übereinstimmung"
msgid "Enable filament dynamic map"
msgstr "Dynamische Filamentzuordnung aktivieren"
msgstr ""
msgid "Enable dynamic filament mapping during print."
msgstr "Dynamische Filamentzuordnung während des Drucks aktivieren."
msgstr ""
msgid "Has filament switcher"
msgstr "Hat Filamentwechsler"
msgstr ""
msgid "Printer has a filament switcher hardware (e.g., AMS)."
msgstr "Der Drucker verfügt über eine Filamentwechsler-Hardware (z. B. AMS)."
msgstr ""
msgid "Flush temperature"
msgstr "Spültemperatur"
@@ -15415,7 +15376,7 @@ msgstr ""
"unterstützt."
msgid "Z-buckling bias optimization (experimental)"
msgstr "Z-Buckling-Bias-Optimierung (experimentell)"
msgstr ""
msgid ""
"Tightens the gyroid wave along the Z (vertical) axis at low infill density "
@@ -15424,11 +15385,6 @@ msgid ""
"~30% sparse infill density and above. Only applies when Sparse infill "
"pattern is set to Gyroid."
msgstr ""
"Strafft die Gyroid-Welle entlang der Z-Achse (vertikal) bei geringer Fülldichte, "
"um die effektive vertikale Säulenlänge zu verkürzen und die Z-Achsen-Kompressions-"
"Knickfestigkeit zu verbessern. Der Filamentverbrauch bleibt erhalten. Keine "
"Auswirkung bei ~30% einfacher Fülldichte und darüber. Gilt nur, wenn das einfache "
"Füllmuster auf Gyroid eingestellt ist."
msgid "Sparse infill pattern"
msgstr "Füllmuster"
@@ -16318,7 +16274,7 @@ msgstr ""
"bringen.Setze den Wert auf 0, um diese Funktion zu deaktivieren."
msgid "Minimum non-zero part cooling fan speed"
msgstr "Minimale nicht-null Lüftergeschwindigkeit für die Teilekühlung"
msgstr ""
msgid ""
"Some part-cooling fans cannot start spinning when commanded below a certain "
@@ -16337,11 +16293,6 @@ msgid ""
"below the one you know it can actually spool at.\n"
"Set to 0 to deactivate."
msgstr ""
"Einige Teilekühlventilatoren können nicht zu drehen beginnen, wenn sie unter einem bestimmten PWM-Arbeitszyklus befehligt werden. Wenn dieser Wert über 0 eingestellt ist, wird jeder nicht-null-Teilekühlventilatorbefehl auf mindestens diesen Prozentsatz angehoben, damit der Lüfter zuverlässig startet. Ein Lüfterbefehl von 0 (Lüfter aus) wird immer genau eingehalten. Diese Begrenzung wird nach jeder anderen Lüfterberechnung angewendet (Erstschicht-Ramp-up, Schichtzeit-Interpolation, Überhangs-/Brücken-/Stützstruktur-Schnittstellen-/Glättungsüberschreibungen), sodass die Skalierung weiterhin im Bereich [dieser Wert, 100%] erfolgt.\n"
"\n"
"Wenn Ihre Firmware den Lüfter bereits unter einem Schwellenwert deaktiviert (z.B. Klipper's [fan] off_below: 0.10 schaltet den Lüfter aus, wenn der befehligte Arbeitszyklus unter 10% liegt), sollten idealerweise dieser Wert und der Firmware-Schwellenwert auf denselben Wert eingestellt werden. Wenn sie übereinstimmen (z.B. off_below: 0.10 in Klipper und 10% hier), garantiert der Slicer, dass er nie einen nicht-null-Wert emittiert, den die Firmware stillschweigend fallen lässt, und der Lüfter nie einen Wert erhält, der unter dem Wert liegt, den er tatsächlich anfahren kann.\n"
"\n"
"Setze den Wert auf 0, um diese Funktion zu deaktivieren."
msgid "%"
msgstr "%"
@@ -18432,7 +18383,7 @@ msgid "Enable filament ramming"
msgstr "Erlaube Filamentrammen"
msgid "Tool change on wipe tower"
msgstr "Werkzeugwechsel auf dem Reinigungsturm"
msgstr ""
msgid ""
"Force the toolhead to travel to the wipe tower before issuing the tool "
@@ -18443,8 +18394,6 @@ msgid ""
"this option if you want the tool change to always be issued above the wipe "
"tower instead."
msgstr ""
"Erzwinge, dass der Werkzeugkopf zum Reinigungsturm fährt, bevor der Werkzeugwechselbefehl (Tx) ausgegeben wird. Nur relevant für Mehrfach-Extruder (Mehrfach-Werkzeugkopf) Drucker, die einen Typ-2-Reinigungsturm verwenden. Standardmäßig überspringt Orca die Fahrt auf Mehrfach-Werkzeugkopf-Maschinen, da die Firmware den Kopfwechsel übernimmt, was dazu führen kann, dass der Tx-Befehl über dem gedruckten Teil ausgegeben wird. Aktivieren Sie diese Option, wenn Sie möchten, dass der Werkzeugwechsel immer über dem Reinigungsturm ausgegeben wird."
msgid "No sparse layers (beta)"
msgstr "Keine dünnen Schichten (Beta)"
@@ -19396,7 +19345,7 @@ msgstr ""
"verschiedene Materialien aufeinandertreffen."
msgid "Cool down from interface boost during prime tower"
msgstr "Abkühlung von der Schnittstellen-Boost während des Reinigungsturms"
msgstr "^"
msgid ""
"When interface-layer temperature boost is active, set the nozzle back to "
@@ -22045,18 +21994,12 @@ msgid ""
"\n"
"Available nozzle profiles for this printer:"
msgstr ""
"\n"
"\n"
"Verfügbare Düsenprofile für diesen Drucker:"
msgid ""
"\n"
"\n"
"Choose YES to switch existing preset:"
msgstr ""
"\n"
"\n"
"Wählen Sie JA, um das vorhandene Profil zu wechseln:"
msgid "Printer Created Successfully"
msgstr "Drucker erfolgreich erstellt"
@@ -23265,16 +23208,16 @@ msgid "Detection radius"
msgstr "Erkennungsradius"
msgid "Selected"
msgstr "Ausgewählt"
msgstr ""
msgid "Auto-generate"
msgstr "Automatisch generieren"
msgstr ""
msgid "Generate brim ears using Max angle and Detection radius"
msgstr "Mausohren mit Maximalwinkel und Erkennungsradius generieren"
msgstr ""
msgid "Add or Select"
msgstr "Hinzufügen oder auswählen"
msgstr ""
msgid ""
"Warning: The brim type is not set to \"painted\", the brim ears will not "
@@ -23287,7 +23230,7 @@ msgid "Set the brim type of this object to \"painted\""
msgstr "Den Brim-Typ dieses Objekts auf \"bemalt\" setzen"
msgid "invalid brim ears"
msgstr "Ungültige Mausohren"
msgstr ""
msgid "Brim Ears"
msgstr "Mausohren"

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because one or more lines are too long

View File

@@ -1 +1 @@
02.00.00.30
02.00.00.29

View File

@@ -472,10 +472,6 @@
"name": "Anycubic Generic PA-CF",
"sub_path": "filament/Anycubic Generic PA-CF.json"
},
{
"name": "Fiberon PA6-CF20 @Anycubic Kobra S1",
"sub_path": "filament/Polymaker/Fiberon PA6-CF20 @Anycubic Kobra S1.json"
},
{
"name": "Anycubic Generic PC",
"sub_path": "filament/Anycubic Generic PC.json"
@@ -592,18 +588,6 @@
"name": "Anycubic PLA+ @Anycubic Kobra X 0.4 nozzle",
"sub_path": "filament/Anycubic PLA+ @Anycubic Kobra X 0.4 nozzle.json"
},
{
"name": "Panchroma PLA @Anycubic Kobra S1",
"sub_path": "filament/Polymaker/Panchroma PLA @Anycubic Kobra S1.json"
},
{
"name": "Polymaker PLA Pro @Anycubic Kobra S1",
"sub_path": "filament/Polymaker/Polymaker PLA Pro @Anycubic Kobra S1.json"
},
{
"name": "Polymaker PLA Pro Metallic @Anycubic Kobra S1",
"sub_path": "filament/Polymaker/Polymaker PLA Pro Metallic @Anycubic Kobra S1.json"
},
{
"name": "Anycubic Generic PVA",
"sub_path": "filament/Anycubic Generic PVA.json"

View File

@@ -1,81 +0,0 @@
{
"type": "filament",
"name": "Fiberon PA6-CF20 @Anycubic Kobra S1",
"inherits": "fdm_filament_pa",
"from": "system",
"setting_id": "GFSL57_AC",
"filament_id": "GFL57",
"instantiation": "true",
"compatible_printers": [
"Anycubic Kobra S1 0.4 nozzle"
],
"filament_vendor": [
"Polymaker"
],
"filament_type": [
"PA6-CF"
],
"eng_plate_temp": [
"40"
],
"hot_plate_temp": [
"40"
],
"textured_plate_temp": [
"40"
],
"eng_plate_temp_initial_layer": [
"40"
],
"hot_plate_temp_initial_layer": [
"40"
],
"textured_plate_temp_initial_layer": [
"40"
],
"overhang_fan_speed": [
"100"
],
"filament_flow_ratio": [
"1.03"
],
"reduce_fan_stop_start_freq": [
"1"
],
"fan_cooling_layer_time": [
"30"
],
"filament_cost": [
"79.98"
],
"filament_density": [
"1.17"
],
"filament_max_volumetric_speed": [
"7.5"
],
"filament_retraction_length": [
"1.0"
],
"filament_z_hop": [
"0.0"
],
"nozzle_temperature_initial_layer": [
"300"
],
"fan_max_speed": [
"30"
],
"slow_down_layer_time": [
"6"
],
"nozzle_temperature": [
"300"
],
"temperature_vitrification": [
"74.2"
],
"nozzle_temperature_range_low": [
"280"
]
}

View File

@@ -1,70 +0,0 @@
{
"type": "filament",
"name": "Panchroma PLA @Anycubic Kobra S1",
"inherits": "fdm_filament_pla",
"from": "system",
"setting_id": "GFSPM001_AC",
"filament_id": "GFPM001",
"instantiation": "true",
"compatible_printers": [
"Anycubic Kobra S1 0.4 nozzle"
],
"filament_vendor": [
"Polymaker"
],
"filament_type": [
"PLA"
],
"cool_plate_temp": [
"50"
],
"eng_plate_temp": [
"50"
],
"hot_plate_temp": [
"50"
],
"textured_plate_temp": [
"50"
],
"cool_plate_temp_initial_layer": [
"50"
],
"eng_plate_temp_initial_layer": [
"50"
],
"hot_plate_temp_initial_layer": [
"50"
],
"textured_plate_temp_initial_layer": [
"50"
],
"filament_flow_ratio": [
"0.88"
],
"filament_cost": [
"0"
],
"filament_density": [
"1.32"
],
"filament_max_volumetric_speed": [
"20"
],
"slow_down_min_speed": [
"5"
],
"slow_down_layer_time": [
"10"
],
"temperature_vitrification": [
"62.5"
],
"additional_cooling_fan_speed": [
"0"
],
"enable_pressure_advance": [
"1"
]
}

View File

@@ -1,66 +0,0 @@
{
"type": "filament",
"name": "Polymaker PLA Pro @Anycubic Kobra S1",
"inherits": "fdm_filament_pla",
"from": "system",
"setting_id": "GFSL79_AC",
"filament_id": "GFL79",
"instantiation": "true",
"compatible_printers": [
"Anycubic Kobra S1 0.4 nozzle"
],
"filament_vendor": [
"Polymaker"
],
"filament_type": [
"PLA"
],
"cool_plate_temp": [
"50"
],
"eng_plate_temp": [
"50"
],
"hot_plate_temp": [
"50"
],
"textured_plate_temp": [
"50"
],
"cool_plate_temp_initial_layer": [
"50"
],
"eng_plate_temp_initial_layer": [
"50"
],
"hot_plate_temp_initial_layer": [
"50"
],
"textured_plate_temp_initial_layer": [
"50"
],
"filament_flow_ratio": [
"0.85"
],
"filament_cost": [
"0"
],
"filament_density": [
"1.23"
],
"filament_max_volumetric_speed": [
"16"
],
"slow_down_min_speed": [
"5"
],
"slow_down_layer_time": [
"10"
],
"temperature_vitrification": [
"55"
],
"additional_cooling_fan_speed": [
"0"
]
}

View File

@@ -1,66 +0,0 @@
{
"type": "filament",
"name": "Polymaker PLA Pro Metallic @Anycubic Kobra S1",
"inherits": "fdm_filament_pla",
"from": "system",
"setting_id": "GFSL80_AC",
"filament_id": "GFL80",
"instantiation": "true",
"compatible_printers": [
"Anycubic Kobra S1 0.4 nozzle"
],
"filament_vendor": [
"Polymaker"
],
"filament_type": [
"PLA"
],
"cool_plate_temp": [
"50"
],
"eng_plate_temp": [
"50"
],
"hot_plate_temp": [
"50"
],
"textured_plate_temp": [
"50"
],
"cool_plate_temp_initial_layer": [
"50"
],
"eng_plate_temp_initial_layer": [
"50"
],
"hot_plate_temp_initial_layer": [
"50"
],
"textured_plate_temp_initial_layer": [
"50"
],
"filament_flow_ratio": [
"0.85"
],
"filament_cost": [
"0"
],
"filament_density": [
"1.23"
],
"filament_max_volumetric_speed": [
"16"
],
"slow_down_min_speed": [
"5"
],
"slow_down_layer_time": [
"10"
],
"temperature_vitrification": [
"55"
],
"additional_cooling_fan_speed": [
"0"
]
}

View File

@@ -33,10 +33,10 @@
"disable_m73": "0",
"gcode_flavor": "klipper",
"printable_area": [
"0x0",
"426x0",
"426x420",
"0x420"
"3x0",
"423x0",
"423x420",
"3x420"
],
"printable_height": "501",
"thumbnails": "230x110/PNG",
@@ -48,24 +48,7 @@
"bbl_use_printhost": "0",
"bed_custom_model": "",
"bed_custom_texture": "",
"bed_exclude_area": [
"0x0",
"3x0",
"3x420",
"0x420",
"0x0",
"423x0",
"423x0",
"423x0",
"423x0",
"426x0",
"426x420",
"423x420",
"423x0",
"0x0",
"0x0",
"0x0"
],
"bed_exclude_area": [],
"bed_mesh_max": "0,0",
"bed_mesh_min": "0,0",
"bed_mesh_probe_distance": "0,0",

View File

@@ -23,10 +23,10 @@
"disable_m73": "0",
"gcode_flavor": "klipper",
"printable_area": [
"0x0",
"426x0",
"426x420",
"0x420"
"3x0",
"423x0",
"423x420",
"3x420"
],
"printable_height": "501",
"thumbnails": "230x110/PNG",
@@ -38,24 +38,7 @@
"bbl_use_printhost": "0",
"bed_custom_model": "",
"bed_custom_texture": "",
"bed_exclude_area": [
"0x0",
"3x0",
"3x420",
"0x420",
"0x0",
"423x0",
"423x0",
"423x0",
"423x0",
"426x0",
"426x420",
"423x420",
"423x0",
"0x0",
"0x0",
"0x0"
],
"bed_exclude_area": [],
"bed_mesh_max": "0,0",
"bed_mesh_min": "0,0",
"bed_mesh_probe_distance": "0,0",

View File

@@ -23,10 +23,10 @@
"disable_m73": "0",
"gcode_flavor": "klipper",
"printable_area": [
"0x0",
"426x0",
"426x420",
"0x420"
"3x0",
"423x0",
"423x420",
"3x420"
],
"printable_height": "501",
"thumbnails": "230x110/PNG",
@@ -38,24 +38,7 @@
"bbl_use_printhost": "0",
"bed_custom_model": "",
"bed_custom_texture": "",
"bed_exclude_area": [
"0x0",
"3x0",
"3x420",
"0x420",
"0x0",
"423x0",
"423x0",
"423x0",
"423x0",
"426x0",
"426x420",
"423x420",
"423x0",
"0x0",
"0x0",
"0x0"
],
"bed_exclude_area": [],
"bed_mesh_max": "0,0",
"bed_mesh_min": "0,0",
"bed_mesh_probe_distance": "0,0",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "10000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "450",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -201,10 +201,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "150",
@@ -271,7 +271,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.22",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "4",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "30%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "10000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "430",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "80",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "3dhoneycomb",
"sparse_infill_speed": "180",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -201,10 +201,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "150",
@@ -271,7 +271,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "180",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "200",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "300",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "30%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "300",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -201,10 +201,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "300",
@@ -271,7 +271,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "350",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "80",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.62",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "200",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "200",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "200",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "200",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "30%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "300",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -201,10 +201,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "300",
@@ -271,7 +271,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.82",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "270",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "outer wall/inner wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "300",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "30%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "200",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -201,10 +201,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "150",
@@ -271,7 +271,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "80",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.62",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.82",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "230",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "120",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "120",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -201,10 +201,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "150",
@@ -271,7 +271,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "120",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "200",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "30%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "200",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "10000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.62",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "100",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "80",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.62",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.82",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "80",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.62",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.82",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "100",
@@ -256,7 +256,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_infill_order": "inner wall/outer wall/infill",
"wall_loops": "2",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.82",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "80",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.62",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -212,10 +212,10 @@
"smooth_coefficient": "40",
"smooth_speed_discontinuity_area": "1",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "5000",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.82",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -285,7 +285,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -190,10 +190,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "450",
@@ -251,7 +251,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",
"wall_transition_angle": "10",

View File

@@ -195,10 +195,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.22",
"sparse_infill_pattern": "gyroid",
"sparse_infill_speed": "100",
@@ -265,7 +265,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "4",
"wall_sequence": "inner wall/outer wall",

View File

@@ -186,10 +186,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"spiral_mode": "0",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -188,10 +188,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"spiral_mode": "0",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -187,10 +187,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "gyroid",
"spiral_mode": "0",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -188,10 +188,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"spiral_mode": "0",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -191,10 +191,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"spiral_mode": "0",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -192,10 +192,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "270",
@@ -251,7 +251,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",
"wall_transition_angle": "10",

View File

@@ -181,10 +181,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "arachne",
"wall_loops": "3",
"wall_sequence": "inner wall/outer wall",

View File

@@ -181,10 +181,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "150",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "arachne",
"wall_loops": "3",
"wall_sequence": "outer wall/inner wall",

View File

@@ -181,10 +181,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "200",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -181,10 +181,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "crosshatch",
"sparse_infill_speed": "200",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -189,10 +189,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "25%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "270",
@@ -251,7 +251,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "arachne",
"wall_sequence": "inner wall/outer wall",
"wall_transition_angle": "10",

View File

@@ -186,10 +186,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "230",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -195,10 +195,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.62",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "100",
@@ -265,7 +265,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "classic",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -195,10 +195,10 @@
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_direction": "45",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.82",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "100",
@@ -265,7 +265,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

View File

@@ -188,10 +188,10 @@
],
"small_perimeter_speed": "50%",
"small_perimeter_threshold": "0",
"solid_infill_filament": "0",
"solid_infill_filament": "1",
"sparse_infill_acceleration": "100%",
"sparse_infill_density": "15%",
"sparse_infill_filament": "0",
"sparse_infill_filament": "1",
"sparse_infill_line_width": "0.45",
"sparse_infill_pattern": "grid",
"sparse_infill_speed": "200",
@@ -250,7 +250,7 @@
"tree_support_wall_count": "0",
"wall_direction": "auto",
"wall_distribution_count": "1",
"wall_filament": "0",
"wall_filament": "1",
"wall_generator": "arachne",
"wall_loops": "2",
"wall_sequence": "inner wall/outer wall",

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +0,0 @@
{
"type": "filament",
"name": "Fiberon PA12-CF10 @BBL X1",
"inherits": "Fiberon PA12-CF10 @base",
"from": "system",
"setting_id": "GFSL56_00",
"instantiation": "true",
"compatible_printers": [
"Bambu Lab X1 0.4 nozzle",
"Bambu Lab X1 Carbon 0.4 nozzle",
"Bambu Lab P1S 0.4 nozzle",
"Bambu Lab X1E 0.4 nozzle"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
]
}

View File

@@ -1,77 +0,0 @@
{
"type": "filament",
"name": "Fiberon PA12-CF10 @base",
"inherits": "fdm_filament_pa",
"from": "system",
"filament_id": "GFL56",
"instantiation": "false",
"bed_type": [
"Cool Plate"
],
"eng_plate_temp": [
"40"
],
"eng_plate_temp_initial_layer": [
"40"
],
"fan_cooling_layer_time": [
"15"
],
"fan_max_speed": [
"100"
],
"filament_cost": [
"99.99"
],
"filament_density": [
"1.06"
],
"filament_flow_ratio": [
"0.95"
],
"filament_max_volumetric_speed": [
"14"
],
"filament_type": [
"PA-CF"
],
"filament_vendor": [
"Polymaker"
],
"full_fan_speed_layer": [
"2"
],
"hot_plate_temp": [
"40"
],
"hot_plate_temp_initial_layer": [
"40"
],
"nozzle_temperature": [
"300"
],
"nozzle_temperature_initial_layer": [
"300"
],
"nozzle_temperature_range_low": [
"280"
],
"overhang_fan_speed": [
"100"
],
"reduce_fan_stop_start_freq": [
"1"
],
"slow_down_min_speed": [
"10"
],
"temperature_vitrification": [
"55"
],
"textured_plate_temp": [
"40"
],
"textured_plate_temp_initial_layer": [
"40"
]
}

View File

@@ -1,18 +0,0 @@
{
"type": "filament",
"name": "Fiberon PA6-CF20 @BBL X1",
"inherits": "Fiberon PA6-CF20 @base",
"from": "system",
"setting_id": "GFSL57_00",
"instantiation": "true",
"compatible_printers": [
"Bambu Lab X1 0.4 nozzle",
"Bambu Lab X1 Carbon 0.4 nozzle",
"Bambu Lab P1S 0.4 nozzle",
"Bambu Lab X1E 0.4 nozzle"
],
"filament_extruder_variant": [
"Direct Drive Standard",
"Direct Drive High Flow"
]
}

View File

@@ -1,74 +0,0 @@
{
"type": "filament",
"name": "Fiberon PA6-CF20 @base",
"inherits": "fdm_filament_pa",
"from": "system",
"filament_id": "GFL57",
"instantiation": "false",
"bed_type": [
"Cool Plate"
],
"eng_plate_temp": [
"40"
],
"eng_plate_temp_initial_layer": [
"40"
],
"fan_cooling_layer_time": [
"15"
],
"fan_max_speed": [
"100"
],
"filament_cost": [
"83.99"
],
"filament_density": [
"1.17"
],
"filament_flow_ratio": [
"0.95"
],
"filament_max_volumetric_speed": [
"14"
],
"filament_type": [
"PA6-CF"
],
"filament_vendor": [
"Polymaker"
],
"hot_plate_temp": [
"40"
],
"hot_plate_temp_initial_layer": [
"40"
],
"nozzle_temperature": [
"300"
],
"nozzle_temperature_initial_layer": [
"300"
],
"nozzle_temperature_range_low": [
"280"
],
"overhang_fan_speed": [
"100"
],
"reduce_fan_stop_start_freq": [
"1"
],
"slow_down_min_speed": [
"10"
],
"temperature_vitrification": [
"74.2"
],
"textured_plate_temp": [
"40"
],
"textured_plate_temp_initial_layer": [
"40"
]
}

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