prusaslicer-py 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. prusaslicer_py-0.1.0/.github/dependabot.yml +20 -0
  2. prusaslicer_py-0.1.0/.github/workflows/ci.yml +70 -0
  3. prusaslicer_py-0.1.0/.github/workflows/engine.yml +158 -0
  4. prusaslicer_py-0.1.0/.github/workflows/release.yml +87 -0
  5. prusaslicer_py-0.1.0/.gitignore +89 -0
  6. prusaslicer_py-0.1.0/.pre-commit-config.yaml +21 -0
  7. prusaslicer_py-0.1.0/AGENTS.md +120 -0
  8. prusaslicer_py-0.1.0/CHANGELOG.md +64 -0
  9. prusaslicer_py-0.1.0/CONTRIBUTING.md +54 -0
  10. prusaslicer_py-0.1.0/LICENSE +216 -0
  11. prusaslicer_py-0.1.0/PKG-INFO +190 -0
  12. prusaslicer_py-0.1.0/README.md +167 -0
  13. prusaslicer_py-0.1.0/SECURITY.md +33 -0
  14. prusaslicer_py-0.1.0/docs/DECISIONS.md +332 -0
  15. prusaslicer_py-0.1.0/examples/basic_slicing.py +41 -0
  16. prusaslicer_py-0.1.0/examples/torus_example_attempt.py +49 -0
  17. prusaslicer_py-0.1.0/justfile +54 -0
  18. prusaslicer_py-0.1.0/prusaslicer_py/__init__.py +15 -0
  19. prusaslicer_py-0.1.0/prusaslicer_py/slicer.py +402 -0
  20. prusaslicer_py-0.1.0/pyproject.toml +62 -0
  21. prusaslicer_py-0.1.0/scripts/01_store_helps.py +38 -0
  22. prusaslicer_py-0.1.0/scripts/02_json_cli.py +179 -0
  23. prusaslicer_py-0.1.0/scripts/03_restructure_cli.py +102 -0
  24. prusaslicer_py-0.1.0/tests/__init__.py +0 -0
  25. prusaslicer_py-0.1.0/tests/conftest.py +40 -0
  26. prusaslicer_py-0.1.0/tests/fixtures/synthetic_help_output.txt +30 -0
  27. prusaslicer_py-0.1.0/tests/test_cli_extraction.py +275 -0
  28. prusaslicer_py-0.1.0/tests/test_engine.py +51 -0
  29. prusaslicer_py-0.1.0/tests/test_slice_output.py +182 -0
  30. prusaslicer_py-0.1.0/tests/test_slicer.py +110 -0
  31. prusaslicer_py-0.1.0/uv.lock +381 -0
@@ -0,0 +1,20 @@
1
+ version: 2
2
+ updates:
3
+ # The pins live in pyproject.toml's [dependency-groups] and uv.lock; there is
4
+ # no requirements.txt left for the pip ecosystem to read.
5
+ - package-ecosystem: uv
6
+ directory: /
7
+ schedule:
8
+ interval: weekly
9
+ commit-message:
10
+ prefix: build
11
+ groups:
12
+ python:
13
+ patterns: ["*"]
14
+
15
+ - package-ecosystem: github-actions
16
+ directory: /
17
+ schedule:
18
+ interval: weekly
19
+ commit-message:
20
+ prefix: ci
@@ -0,0 +1,70 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ # No branch filter: a stacked pull request is based on its parent branch, not
7
+ # on main, and a base change fires `edited`, which is not a default activity
8
+ # type -- so filtering on main means a retargeted PR gets no checks at all.
9
+ pull_request:
10
+
11
+ permissions:
12
+ contents: read
13
+
14
+ concurrency:
15
+ group: ci-${{ github.ref }}
16
+ cancel-in-progress: true
17
+
18
+ jobs:
19
+ check:
20
+ name: Check
21
+ runs-on: ubuntu-latest
22
+ timeout-minutes: 10
23
+ steps:
24
+ - uses: actions/checkout@v7
25
+ - uses: extractions/setup-just@v4
26
+ - uses: astral-sh/setup-uv@v7
27
+ with:
28
+ enable-cache: true
29
+ - run: just setup
30
+ - run: just check
31
+
32
+ test:
33
+ name: Test (Python ${{ matrix.python-version }}, ${{ matrix.os }})
34
+ runs-on: ${{ matrix.os }}
35
+ timeout-minutes: 10
36
+ strategy:
37
+ fail-fast: false
38
+ matrix:
39
+ python-version: ["3.11", "3.12", "3.13"]
40
+ os: [ubuntu-latest]
41
+ include:
42
+ - python-version: "3.11"
43
+ os: windows-latest
44
+ steps:
45
+ - uses: actions/checkout@v7
46
+ - uses: extractions/setup-just@v4
47
+ - uses: astral-sh/setup-uv@v7
48
+ with:
49
+ enable-cache: true
50
+ python-version: ${{ matrix.python-version }}
51
+ # PrusaSlicer is not installed on these runners, so the engine-dependent
52
+ # tests skip. That is an environment fault, not a pass -- the skip count
53
+ # in the log says how many tests were withheld. Do not set
54
+ # PRUSASLICER_PY_REQUIRE_ENGINE here until a runner actually has the
55
+ # engine; see docs/DECISIONS.md D3.
56
+ - run: just setup
57
+ - run: just test
58
+
59
+ ok:
60
+ name: ok
61
+ if: always()
62
+ needs: [check, test]
63
+ runs-on: ubuntu-latest
64
+ timeout-minutes: 5
65
+ steps:
66
+ - name: Fail if any upstream job failed or was cancelled
67
+ if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
68
+ run: exit 1
69
+ - name: Pass
70
+ run: echo 'All checks passed'
@@ -0,0 +1,158 @@
1
+ name: Engine
2
+
3
+ # The real engine, across the ways people actually install it.
4
+ #
5
+ # ci.yml runs the mocked suite everywhere and SKIPS the engine tests, because
6
+ # no standard runner has PrusaSlicer. That leaves the end-to-end slice path --
7
+ # the reason this package exists -- covered by stubs alone, and stubs are what
8
+ # hid `--version` (which PrusaSlicer has never supported) and the fact that a
9
+ # Flatpak install was invisible to discovery.
10
+ #
11
+ # Every job here sets PRUSASLICER_PY_REQUIRE_ENGINE=1, so a failed install is a
12
+ # LOUD failure rather than a green run full of skips. A job that cannot get the
13
+ # engine must not look like a job that tested it.
14
+ #
15
+ # Not on every push: these install a large GUI application and are slower and
16
+ # more fragile than the gate. Run on demand, and weekly so upstream packaging
17
+ # changes surface without anyone remembering to look.
18
+
19
+ on:
20
+ workflow_dispatch:
21
+ schedule:
22
+ - cron: "0 6 * * 1" # Mondays, 06:00 UTC
23
+
24
+ permissions:
25
+ contents: read
26
+
27
+ concurrency:
28
+ group: engine-${{ github.ref }}
29
+ cancel-in-progress: true
30
+
31
+ env:
32
+ PRUSASLICER_PY_REQUIRE_ENGINE: "1"
33
+
34
+ jobs:
35
+ flatpak:
36
+ name: Linux / Flatpak (Flathub)
37
+ runs-on: ubuntu-latest
38
+ timeout-minutes: 45
39
+ steps:
40
+ - uses: actions/checkout@v7
41
+ - uses: extractions/setup-just@v4
42
+ - uses: astral-sh/setup-uv@v7
43
+ with:
44
+ enable-cache: true
45
+ - name: Install PrusaSlicer from Flathub
46
+ run: |
47
+ sudo apt-get update
48
+ sudo apt-get install -y flatpak
49
+ sudo flatpak remote-add --if-not-exists flathub \
50
+ https://dl.flathub.org/repo/flathub.flatpakrepo
51
+ sudo flatpak install -y --noninteractive flathub com.prusa3d.PrusaSlicer
52
+ - name: Show what was installed
53
+ run: flatpak info com.prusa3d.PrusaSlicer | head -20
54
+ - run: just setup
55
+ - name: Full suite, engine required
56
+ # PRUSASLICER_PY_REQUIRE_ENGINE is set at workflow level, so a missing
57
+ # engine fails here rather than skipping. `just test` rather than
58
+ # `just test-engine`: the latter prefixes `VAR=x cmd`, which is
59
+ # POSIX-only and does not run on the Windows job.
60
+ run: just test
61
+ - name: Capture and extract the CLI surface
62
+ run: |
63
+ just capture-cli
64
+ just extract-cli
65
+ - name: Full suite again, now with the captured corpus present
66
+ # The corpus tests skip when scripts/01_helps is empty; this is the one
67
+ # place they actually run, against a real PrusaSlicer's real --help.
68
+ run: just test
69
+
70
+ linux-apt:
71
+ name: Linux / distro package on PATH
72
+ runs-on: ubuntu-latest
73
+ timeout-minutes: 45
74
+ steps:
75
+ - uses: actions/checkout@v7
76
+ - uses: extractions/setup-just@v4
77
+ - uses: astral-sh/setup-uv@v7
78
+ with:
79
+ enable-cache: true
80
+ - name: Install PrusaSlicer from the distro archive
81
+ # Was an AppImage job. PrusaSlicer publishes NO Linux assets on its
82
+ # GitHub releases -- checked across the last five, including 2.9.6:
83
+ # only setup.exe, .dmg and a Windows .zip. Linux is Flathub and distro
84
+ # packages, so this exercises plain PATH discovery via the distro
85
+ # package rather than an artifact that does not exist.
86
+ run: |
87
+ set -euo pipefail
88
+ sudo apt-get update
89
+ sudo apt-get install -y prusa-slicer
90
+ command -v prusa-slicer
91
+ prusa-slicer --help | head -1
92
+ - run: just setup
93
+ - name: Full suite, engine required
94
+ run: just test
95
+
96
+ macos:
97
+ name: macOS / Homebrew cask
98
+ runs-on: macos-latest
99
+ timeout-minutes: 45
100
+ steps:
101
+ - uses: actions/checkout@v7
102
+ - uses: extractions/setup-just@v4
103
+ - uses: astral-sh/setup-uv@v7
104
+ with:
105
+ enable-cache: true
106
+ - name: Install PrusaSlicer
107
+ run: |
108
+ set -euo pipefail
109
+ brew install --cask prusaslicer
110
+ # The cask installs an .app bundle; expose the CLI on PATH.
111
+ sudo ln -sf "/Applications/PrusaSlicer.app/Contents/MacOS/PrusaSlicer" \
112
+ /usr/local/bin/prusa-slicer
113
+ command -v prusa-slicer
114
+ - run: just setup
115
+ - name: Full suite, engine required
116
+ # PRUSASLICER_PY_REQUIRE_ENGINE is set at workflow level, so a missing
117
+ # engine fails here rather than skipping. `just test` rather than
118
+ # `just test-engine`: the latter prefixes `VAR=x cmd`, which is
119
+ # POSIX-only and does not run on the Windows job.
120
+ run: just test
121
+
122
+ windows:
123
+ name: Windows / official portable zip
124
+ runs-on: windows-latest
125
+ timeout-minutes: 45
126
+ steps:
127
+ - uses: actions/checkout@v7
128
+ - uses: extractions/setup-just@v4
129
+ - uses: astral-sh/setup-uv@v7
130
+ with:
131
+ enable-cache: true
132
+ - name: Install PrusaSlicer from the official portable zip
133
+ # Was `choco install prusaslicer`. The package exists, but its install
134
+ # location is not something to guess at, and a recursive sweep of
135
+ # Program Files reported "not found" for an install that had probably
136
+ # succeeded. The upstream .zip is the same build the installer ships,
137
+ # needs no admin, pins an exact version, and lands the layout discovery
138
+ # expects: <dir>/prusa-slicer-console.exe beside <dir>/resources/shapes.
139
+ shell: pwsh
140
+ run: |
141
+ $ErrorActionPreference = 'Stop'
142
+ $rel = Invoke-RestMethod https://api.github.com/repos/prusa3d/PrusaSlicer/releases/latest
143
+ $asset = $rel.assets | Where-Object { $_.name -like '*.zip' } | Select-Object -First 1
144
+ if (-not $asset) { throw "no .zip asset in $($rel.tag_name)" }
145
+ Write-Host "downloading $($asset.name)"
146
+ Invoke-WebRequest $asset.browser_download_url -OutFile prusaslicer.zip
147
+ Expand-Archive prusaslicer.zip -DestinationPath "$env:RUNNER_TEMP\ps"
148
+ $exe = Get-ChildItem "$env:RUNNER_TEMP\ps" -Recurse -Filter 'prusa-slicer-console.exe' |
149
+ Select-Object -First 1
150
+ if (-not $exe) { throw 'prusa-slicer-console.exe not present in the zip' }
151
+ Write-Host "found: $($exe.FullName)"
152
+ Add-Content -Path $env:GITHUB_PATH -Value $exe.DirectoryName
153
+ - name: Confirm it is on PATH
154
+ shell: pwsh
155
+ run: (Get-Command prusa-slicer-console.exe).Source
156
+ - run: just setup
157
+ - name: Full suite, engine required
158
+ run: just test
@@ -0,0 +1,87 @@
1
+ name: Release
2
+
3
+ # Publishes to PyPI via Trusted Publishing (OIDC). No API token is stored.
4
+ #
5
+ # The publisher on PyPI is pinned to heibench/prusaslicer-py, this workflow
6
+ # filename, and the `pypi` environment -- all three must match or the publish
7
+ # is refused.
8
+
9
+ on:
10
+ release:
11
+ types: [published]
12
+ workflow_dispatch:
13
+ inputs:
14
+ expected_version:
15
+ description: "Exact version the dist must carry (e.g. 0.1.0) — guards against publishing the wrong ref"
16
+ required: true
17
+ type: string
18
+
19
+ permissions:
20
+ contents: read
21
+
22
+ jobs:
23
+ build:
24
+ name: Build and verify
25
+ runs-on: ubuntu-latest
26
+ timeout-minutes: 15
27
+ steps:
28
+ - uses: actions/checkout@v7
29
+ - uses: extractions/setup-just@v4
30
+ - uses: astral-sh/setup-uv@v7
31
+ with:
32
+ enable-cache: true
33
+
34
+ - name: Gate on the same checks a pull request runs
35
+ run: |
36
+ just setup
37
+ just check
38
+ just test
39
+
40
+ - name: Build sdist and wheel
41
+ run: uv build
42
+
43
+ - name: Guard built version against the intended release
44
+ # A tag and a version are two statements of one fact; publishing when
45
+ # they disagree ships something nobody asked for under a name someone
46
+ # will trust.
47
+ run: |
48
+ set -eu
49
+ version=$(ls dist/*.tar.gz | sed -E 's|.*/prusaslicer_py-(.+)\.tar\.gz|\1|')
50
+ echo "built: $version"
51
+ case "${{ github.event_name }}" in
52
+ release) want="${GITHUB_REF_NAME#v}" ;;
53
+ workflow_dispatch) want="${{ inputs.expected_version }}" ;;
54
+ esac
55
+ if [ "$version" != "$want" ]; then
56
+ echo "::error::built version '$version' does not match intended release '$want'"
57
+ exit 1
58
+ fi
59
+ case "$version" in
60
+ *.dev*|*rc*|*a[0-9]*|*b[0-9]*)
61
+ echo "::error::refusing to publish pre-release version '$version'"
62
+ exit 1 ;;
63
+ esac
64
+
65
+ - name: Check the metadata PyPI will see
66
+ run: uv run --with twine twine check dist/*
67
+
68
+ - uses: actions/upload-artifact@v7
69
+ with:
70
+ name: dist
71
+ path: dist/
72
+
73
+ publish:
74
+ name: Publish to PyPI
75
+ needs: build
76
+ runs-on: ubuntu-latest
77
+ timeout-minutes: 15
78
+ environment: pypi
79
+ permissions:
80
+ id-token: write # PyPI Trusted Publishing (OIDC)
81
+ steps:
82
+ - uses: actions/download-artifact@v8
83
+ with:
84
+ name: dist
85
+ path: dist/
86
+ - name: Publish
87
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,89 @@
1
+ # Python bytecode files
2
+ __pycache__/
3
+ *.py[cod]
4
+
5
+ # Distribution / packaging
6
+ .Python
7
+ env/
8
+ venv/
9
+ ENV/
10
+ env.bak/
11
+ venv.bak/
12
+
13
+ # Installer logs
14
+ pip-log.txt
15
+ pip-delete-this-directory.txt
16
+
17
+ # mypy
18
+ .mypy_cache/
19
+
20
+ # Pytest
21
+ .cache/
22
+
23
+ # Coverage reports
24
+ .coverage
25
+ .coverage.*
26
+
27
+ # dotenv
28
+ .env
29
+ .env.*
30
+
31
+ # VS Code settings
32
+ .vscode/
33
+
34
+ # PyCharm
35
+ .idea/
36
+
37
+ # Operating system files
38
+ .DS_Store
39
+ Thumbs.db
40
+
41
+ # Jupyter Notebook
42
+ .ipynb_checkpoints/
43
+
44
+ # PrusaSlicer Python egg-info
45
+ prusaslicer_py.egg-info/
46
+
47
+ # Python wheels
48
+ *.whl
49
+
50
+ # Cython build
51
+ build/
52
+ dist/
53
+
54
+ # Sphinx documentation
55
+ docs/_build/
56
+
57
+ # PyInstaller
58
+ *.spec
59
+
60
+ # JetBrains IDE files
61
+ .idea/
62
+
63
+ # Sublime Text project files
64
+ *.sublime-project
65
+ *.sublime-workspace
66
+
67
+ # MacOS Finder files
68
+ ._*
69
+
70
+ # Windows Installer files
71
+ *.cab
72
+ *.msi
73
+ *.msm
74
+ *.msp
75
+
76
+ # Ignore the output directory
77
+ output/
78
+ # uv / virtualenv
79
+ .venv/
80
+
81
+ # Tool caches
82
+ .ruff_cache/
83
+ .pytest_cache/
84
+
85
+ # Captured PrusaSlicer help and everything derived from it (D8).
86
+ # Regenerate with `just capture-cli` + `just extract-cli`.
87
+ scripts/01_helps/
88
+ scripts/02_structured_data/
89
+ scripts/03_restructured_data/
@@ -0,0 +1,21 @@
1
+ repos:
2
+ - repo: https://github.com/gitleaks/gitleaks
3
+ rev: v8.27.2
4
+ hooks:
5
+ - id: gitleaks
6
+
7
+ - repo: https://github.com/pre-commit/pre-commit-hooks
8
+ rev: v5.0.0
9
+ hooks:
10
+ - id: trailing-whitespace
11
+ - id: end-of-file-fixer
12
+ - id: check-yaml
13
+ - id: check-merge-conflict
14
+ - id: check-added-large-files
15
+
16
+ - repo: https://github.com/astral-sh/ruff-pre-commit
17
+ rev: v0.11.12
18
+ hooks:
19
+ - id: ruff
20
+ args: [--fix]
21
+ - id: ruff-format
@@ -0,0 +1,120 @@
1
+ # AGENTS.md
2
+
3
+ Instructions for humans and AI coding agents working in this repository.
4
+
5
+ The org-wide contract at <https://github.com/heibench/.github/blob/main/AGENTS.md>
6
+ is the floor. This file carries what is specific to this repository; where the
7
+ two conflict, this file wins.
8
+
9
+ ## Project
10
+
11
+ `prusaslicer-py` is a **driver**, not a checker. It puts the PrusaSlicer
12
+ command-line interface under program control: locating the executable, invoking
13
+ it, and returning what it established about the engine's output. It does not
14
+ adjudicate a design, and it does not post-process G-code.
15
+
16
+ `scripts/` holds a small extraction pipeline that turns PrusaSlicer's own
17
+ `--help` output into a machine-readable description of its CLI surface
18
+ (`scripts/03_restructured_data/*.json`). That artifact is a large part of what
19
+ this repository is for. Each entry is
20
+ `{option, aliases, value, description}`; see `docs/DECISIONS.md` D6, and treat
21
+ the schema as stable.
22
+
23
+ ## Stack
24
+
25
+ - **Python >= 3.11**, packaged with `hatchling`, dependencies managed by `uv`
26
+ against a committed `uv.lock`.
27
+ - **No runtime dependencies.** The engine boundary is `subprocess`; keep it
28
+ that way. Adding a runtime dependency needs a decision entry.
29
+ - **`prusaslicer_py/slicer.py` is the only module in the package that may
30
+ import `subprocess` or name an executable.** `scripts/01_store_helps.py` is
31
+ the one recorded exception; see `docs/DECISIONS.md` D1.
32
+ - **Tooling** -- `ruff` (format + lint), `mypy` (types), `pytest` (tests),
33
+ `just` (task runner), `pre-commit`.
34
+
35
+ ## Layout
36
+
37
+ ```
38
+ prusaslicer_py/ the driver; the only module that touches the engine
39
+ tests/ pytest suite
40
+ examples/ runnable usage examples
41
+ scripts/ CLI-surface extraction pipeline (01 -> 02 -> 03)
42
+ docs/DECISIONS.md numbered decisions and their reasoning
43
+ ```
44
+
45
+ ## Commands
46
+
47
+ ```sh
48
+ just setup # uv sync
49
+ just fmt # format + autofix
50
+ just check # fmt-check + lint + typecheck (CI-equivalent)
51
+ just test # run tests; engine tests skip if PrusaSlicer is absent
52
+ just test-engine # run tests; engine tests FAIL if PrusaSlicer is absent
53
+ just extract-cli # regenerate scripts/02_ and 03_ data from captured help
54
+ ```
55
+
56
+ Run `just check && just test` before every commit. Never `--no-verify`.
57
+
58
+ ## The engine is optional at test time, and that is load-bearing
59
+
60
+ PrusaSlicer is not installed on most machines that will run this suite,
61
+ including CI. A missing engine is an **environment fault, not a verdict**: it
62
+ says nothing about whether this code is correct.
63
+
64
+ - Tests that need the engine request the `engine` fixture in
65
+ `tests/conftest.py`. It skips when PrusaSlicer is not on `PATH`.
66
+ - Setting `PRUSASLICER_PY_REQUIRE_ENGINE` turns that skip into a hard failure.
67
+ Use it on any machine where the engine is supposed to be present.
68
+ - **A skipped test is not a passing test.** Guard per-test, never at module
69
+ import: a module-level gate reports one skipped line and silently takes every
70
+ test in the file with it.
71
+
72
+ ## Never let silence read as success
73
+
74
+ The engine can exit `0` without producing the file it was asked for. A driver
75
+ call that did not do the thing must not return as though it did:
76
+
77
+ - Verify the artifact before reporting it. `slice_model` checks that the G-code
78
+ file exists and is non-empty, and returns a result describing it.
79
+ - Capture the engine's `stdout`/`stderr` and hand them to the caller. A
80
+ diagnostic that only reaches the parent process's terminal is unavailable to
81
+ the program that needs it.
82
+ - **Decode the engine's output with `errors="replace"`, always.** A strict
83
+ decode raises before the result can be checked, so a byte we cannot read
84
+ becomes a verdict on a slice that succeeded. See `docs/DECISIONS.md` D5.
85
+ - Failure carries the same fields as success. Both `SliceEngineError` and
86
+ `SliceOutputError` expose `output_path`, `returncode`, `stdout` and `stderr`;
87
+ do not put a fact in a message string that the success path returns.
88
+ - Do not add an "assume it worked" escape hatch.
89
+
90
+ ## Status -- what has actually been established
91
+
92
+ Treat these lines as code: if a change makes one false, the change is not
93
+ finished.
94
+
95
+ - **The end-to-end path runs against a real engine on four install modes**,
96
+ in the `Engine` workflow: Flathub Flatpak, a Linux distro package on PATH,
97
+ a macOS Homebrew cask `.app` bundle, and the official Windows portable zip.
98
+ Each runs the whole suite with `PRUSASLICER_PY_REQUIRE_ENGINE=1`, including
99
+ `test_slice_produces_gcode_with_the_real_engine`.
100
+ - **`CI` runs the mocked suite** on Linux (3.11/3.12/3.13) and Windows with no
101
+ engine, so the engine tests skip and the count says how many were withheld.
102
+ - Earlier revisions of this file claimed `check_version` was in regular use on
103
+ Windows. That was false -- it called `--version`, which PrusaSlicer supports
104
+ on no platform. Kept here as a reminder that a Status line is a claim.
105
+
106
+ ## Constraints
107
+
108
+ - Do not hand-edit `scripts/02_structured_data/` or
109
+ `scripts/03_restructured_data/`. They are generated; fix the generator and run
110
+ `just extract-cli`. A test regenerates them and fails if the committed files
111
+ have drifted from the committed parser.
112
+ - Always name an encoding when reading or writing the help output and the JSON
113
+ -- in every stage, `01_store_helps.py` included. The help contains non-ASCII
114
+ characters, and a locale-dependent `open()` is how the data came to say
115
+ `°C`. Decode the engine itself with `errors="replace"`.
116
+ - Do not reimplement PrusaSlicer's arithmetic. The premise is that the engine
117
+ knows what the slice is and we do not.
118
+ - Do not add AI attribution to commits or PR descriptions -- no co-author
119
+ trailers, session links, or "generated with" footers.
120
+ - Do not name, link, or describe any private repository in public output.
@@ -0,0 +1,64 @@
1
+ # Changelog
2
+
3
+ All notable changes to prusaslicer-py are documented here. Follows
4
+ [Keep a Changelog](https://keepachangelog.com) and
5
+ [Semantic Versioning](https://semver.org).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] — 2026-09-06
10
+
11
+ ### Added
12
+
13
+ First release. Notable, given this began as a thin `subprocess` wrapper:
14
+
15
+ - Flatpak discovery. `shutil.which` cannot see a Flatpak — the app is not on
16
+ PATH and there is no binary to find — so a Flathub install, which is how
17
+ PrusaSlicer is normally installed on Linux, previously read as "not
18
+ installed". `PrusaSlicer.engine_kind` reports `path` or `flatpak` so a caller
19
+ can branch (D9).
20
+ - `SliceResult` returned from `slice_model`, carrying `output_path`,
21
+ `size_bytes`, `returncode`, `stdout` and `stderr`. The artifact is verified to
22
+ exist and be non-empty before a result is returned, so "the engine produced
23
+ the file" and "the call did not raise" stop being the same claim (D5).
24
+ - `SliceError`, with `SliceEngineError` and `SliceOutputError` siblings. Both
25
+ carry the same fields, so the failure path is as inspectable as the success
26
+ path.
27
+ - `just capture-cli` regenerates PrusaSlicer's help locally, through the driver
28
+ rather than a hardcoded Windows executable name.
29
+ - Real-engine CI across install modes — Flathub Flatpak, AppImage on PATH,
30
+ Homebrew cask, Chocolatey — each with a missing engine as a hard failure
31
+ rather than a skip.
32
+ - Repository floor: `pyproject` + `uv` with a committed lock, `justfile`, CI on
33
+ Linux and Windows, pre-commit, `AGENTS.md`, `docs/DECISIONS.md`.
34
+
35
+ ### Fixed
36
+
37
+ - `check_version()` called `--version`, which PrusaSlicer has never supported —
38
+ 2.9.6 answers `Unknown option --version` and exits 1. The version is read
39
+ from the first line of `--help`, which is the only place the engine prints
40
+ it. Every stub in the suite answered `--version`, so the tests agreed with
41
+ the code and both were wrong.
42
+ - `get_example_shapes()` ignored an explicitly supplied `slicer_path` and
43
+ re-ran discovery, raising `FileNotFoundError` even when handed a working
44
+ engine.
45
+ - A Flatpak's own files are mounted at `/app` inside the sandbox, so bundled
46
+ example shapes were real to the caller and absent to the engine. Paths under
47
+ the app tree are translated; paths outside it are granted with
48
+ `--filesystem=<dir>`, only for the directories involved.
49
+ - An undecodable byte in the engine's output raised `UnicodeDecodeError` before
50
+ the result could be verified, reporting failure for a slice that succeeded.
51
+ - The CLI-surface extraction dropped 68 distinct options — every one whose
52
+ description began on the following line — mis-split 7 aliased options, and
53
+ carried cp1252 mojibake from a Windows capture. It also discarded any section
54
+ whose name matched none of three hardcoded buckets, which silently lost
55
+ PrusaSlicer 2.9.6's entire `input:` section.
56
+
57
+ ### Changed
58
+
59
+ - Licensed under Apache-2.0, superseding MIT (D7).
60
+ - PrusaSlicer's captured `--help` output is no longer committed; regenerate it
61
+ with `just capture-cli` (D8).
62
+
63
+ [Unreleased]: https://github.com/heibench/prusaslicer-py/compare/v0.1.0...HEAD
64
+ [0.1.0]: https://github.com/heibench/prusaslicer-py/releases/tag/v0.1.0
@@ -0,0 +1,54 @@
1
+ # Contributing
2
+
3
+ ## Getting set up
4
+
5
+ ```sh
6
+ just setup # sync the environment
7
+ just check # format check + lint + typecheck
8
+ just test # the suite
9
+ ```
10
+
11
+ `just check && just test` is what CI runs. Run it before every commit, and
12
+ never bypass hooks.
13
+
14
+ ## Tests and the engine
15
+
16
+ Most of the suite runs without PrusaSlicer installed. The tests that need the
17
+ real engine request the `engine` fixture, which **skips** when it is absent —
18
+ a missing engine is an environment fault, not a verdict on the code.
19
+
20
+ Set `PRUSASLICER_PY_REQUIRE_ENGINE=1` to turn that skip into a hard failure.
21
+ CI does this on the runners that install the engine, so "the engine is missing"
22
+ cannot masquerade as a green run.
23
+
24
+ ```sh
25
+ just test-engine # the whole suite, engine required (POSIX shells)
26
+ ```
27
+
28
+ If you have PrusaSlicer installed, please run it and report what happens. The
29
+ engine is installed several different ways — PATH, Flatpak, AppImage, macOS
30
+ bundle, Windows installer — and discovery has been wrong about at least one of
31
+ them before.
32
+
33
+ ## The CLI surface data
34
+
35
+ `scripts/01_helps/` and everything derived from it are **not committed**: they
36
+ are PrusaSlicer's own output and this repository is Apache-2.0 (D8). Regenerate
37
+ with:
38
+
39
+ ```sh
40
+ just capture-cli # needs PrusaSlicer installed
41
+ just extract-cli
42
+ ```
43
+
44
+ The extraction tests skip without that corpus, and say so.
45
+
46
+ ## Conventions
47
+
48
+ - Conventional Commits: `type(scope): description`, imperative, lowercase, no
49
+ trailing period, subject ≤ 72 characters.
50
+ - One logical change per commit; branch and open a pull request.
51
+ - Decisions with consequences go in [`docs/DECISIONS.md`](docs/DECISIONS.md),
52
+ numbered. Do not relitigate a numbered decision — supersede it.
53
+ - A check that cannot fail is not a check. If you add one, break the thing it
54
+ checks and watch it go red before you trust it.