bambox 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 (64) hide show
  1. bambox-0.1.0/.github/workflows/ci.yml +68 -0
  2. bambox-0.1.0/.github/workflows/prepare-release.yml +128 -0
  3. bambox-0.1.0/.github/workflows/release.yml +218 -0
  4. bambox-0.1.0/.github/workflows/test-pypi.yml +58 -0
  5. bambox-0.1.0/.gitignore +6 -0
  6. bambox-0.1.0/CHANGELOG.md +27 -0
  7. bambox-0.1.0/CLAUDE.md +74 -0
  8. bambox-0.1.0/CONTRIBUTING.md +57 -0
  9. bambox-0.1.0/PKG-INFO +182 -0
  10. bambox-0.1.0/README.md +161 -0
  11. bambox-0.1.0/ROADMAP.md +146 -0
  12. bambox-0.1.0/SECURITY.md +26 -0
  13. bambox-0.1.0/bridge/.gitignore +1 -0
  14. bambox-0.1.0/bridge/Cargo.lock +2344 -0
  15. bambox-0.1.0/bridge/Cargo.toml +28 -0
  16. bambox-0.1.0/bridge/build.rs +13 -0
  17. bambox-0.1.0/bridge/shim/shim.cpp +519 -0
  18. bambox-0.1.0/bridge/src/agent.rs +753 -0
  19. bambox-0.1.0/bridge/src/callbacks.rs +206 -0
  20. bambox-0.1.0/bridge/src/ffi.rs +150 -0
  21. bambox-0.1.0/bridge/src/main.rs +321 -0
  22. bambox-0.1.0/bridge/src/print_job.rs +574 -0
  23. bambox-0.1.0/bridge/src/server.rs +629 -0
  24. bambox-0.1.0/changes/+align-ruff-config.misc +1 -0
  25. bambox-0.1.0/changes/+fix-testpypi-trigger.misc +1 -0
  26. bambox-0.1.0/changes/+remove-dead-cura.misc +1 -0
  27. bambox-0.1.0/changes/.gitkeep +0 -0
  28. bambox-0.1.0/decoy_base.gcode.3mf +0 -0
  29. bambox-0.1.0/docs/daemon-bridge-design.md +213 -0
  30. bambox-0.1.0/docs/decisions/001-template-driven-settings.md +53 -0
  31. bambox-0.1.0/pyproject.toml +70 -0
  32. bambox-0.1.0/src/bambox/__init__.py +24 -0
  33. bambox-0.1.0/src/bambox/assemble.py +47 -0
  34. bambox-0.1.0/src/bambox/bridge.py +513 -0
  35. bambox-0.1.0/src/bambox/cli.py +233 -0
  36. bambox-0.1.0/src/bambox/gcode_compat.py +221 -0
  37. bambox-0.1.0/src/bambox/gcode_templates/p1s_end.gcode.j2 +54 -0
  38. bambox-0.1.0/src/bambox/gcode_templates/p1s_start.gcode.j2 +269 -0
  39. bambox-0.1.0/src/bambox/gcode_templates/p1s_toolchange.gcode.j2 +188 -0
  40. bambox-0.1.0/src/bambox/input_spec.example.json +65 -0
  41. bambox-0.1.0/src/bambox/pack.py +450 -0
  42. bambox-0.1.0/src/bambox/profiles/_uniform_array_keys.json +111 -0
  43. bambox-0.1.0/src/bambox/profiles/_varying_keys.json +41 -0
  44. bambox-0.1.0/src/bambox/profiles/base_p1s.json +569 -0
  45. bambox-0.1.0/src/bambox/profiles/filament_asa.json +41 -0
  46. bambox-0.1.0/src/bambox/profiles/filament_petg_cf.json +41 -0
  47. bambox-0.1.0/src/bambox/profiles/filament_pla.json +41 -0
  48. bambox-0.1.0/src/bambox/settings.py +171 -0
  49. bambox-0.1.0/src/bambox/templates.py +142 -0
  50. bambox-0.1.0/src/bambox/thumbnail.py +112 -0
  51. bambox-0.1.0/src/bambox/toolpath.py +211 -0
  52. bambox-0.1.0/test-cube-petgcf.gcode.3mf +0 -0
  53. bambox-0.1.0/test_box_10x10x5.gcode.3mf +0 -0
  54. bambox-0.1.0/tests/__init__.py +0 -0
  55. bambox-0.1.0/tests/fixtures/plate_1.png +0 -0
  56. bambox-0.1.0/tests/fixtures/plate_1_small.png +0 -0
  57. bambox-0.1.0/tests/fixtures/plate_no_light_1.png +0 -0
  58. bambox-0.1.0/tests/fixtures/project_settings.json +1496 -0
  59. bambox-0.1.0/tests/fixtures/reference.gcode.3mf +0 -0
  60. bambox-0.1.0/tests/test_e2e.py +248 -0
  61. bambox-0.1.0/tests/test_gcode_compat.py +224 -0
  62. bambox-0.1.0/tests/test_pack.py +334 -0
  63. bambox-0.1.0/tests/test_templates.py +219 -0
  64. bambox-0.1.0/uv.lock +637 -0
@@ -0,0 +1,68 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ concurrency:
10
+ group: ci-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ lint:
15
+ runs-on: ubuntu-latest
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+ - uses: astral-sh/setup-uv@v5
19
+ with:
20
+ enable-cache: true
21
+ - run: uv sync --extra dev
22
+ - run: uv run ruff check src tests
23
+ - run: uv run ruff format --check src tests
24
+ - run: uv run mypy src/bambox
25
+
26
+ changelog:
27
+ if: github.event_name == 'pull_request'
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - uses: actions/checkout@v6
31
+ with:
32
+ fetch-depth: 0
33
+ - uses: astral-sh/setup-uv@v5
34
+ with:
35
+ enable-cache: true
36
+ - run: uv sync --extra dev
37
+ - run: uv run towncrier check --compare-with origin/main
38
+
39
+ test:
40
+ runs-on: ${{ matrix.os }}
41
+ strategy:
42
+ matrix:
43
+ os: [ubuntu-latest, macos-latest, windows-latest]
44
+ python-version: ["3.11", "3.12", "3.13"]
45
+ steps:
46
+ - uses: actions/checkout@v6
47
+ - uses: astral-sh/setup-uv@v5
48
+ with:
49
+ python-version: ${{ matrix.python-version }}
50
+ enable-cache: true
51
+ - run: uv sync --extra dev
52
+ - run: uv run pytest -v
53
+
54
+ coverage:
55
+ runs-on: ubuntu-latest
56
+ steps:
57
+ - uses: actions/checkout@v6
58
+ - uses: astral-sh/setup-uv@v5
59
+ with:
60
+ python-version: "3.13"
61
+ enable-cache: true
62
+ - run: uv sync --extra dev
63
+ - run: uv run pytest --cov=bambox --cov-report=xml -v
64
+ - uses: codecov/codecov-action@v5
65
+ with:
66
+ token: ${{ secrets.CODECOV_TOKEN }}
67
+ files: coverage.xml
68
+ fail_ci_if_error: false
@@ -0,0 +1,128 @@
1
+ name: Prepare Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ version:
7
+ description: "Release version (e.g. 0.2.0)"
8
+ required: true
9
+
10
+ jobs:
11
+ prepare:
12
+ runs-on: ubuntu-latest
13
+ permissions:
14
+ contents: write
15
+ pull-requests: write
16
+ steps:
17
+ - uses: actions/checkout@v6
18
+ with:
19
+ fetch-depth: 0
20
+
21
+ - name: Validate version format
22
+ run: |
23
+ VERSION="${{ inputs.version }}"
24
+ if [[ ! "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
25
+ echo "::error::Invalid version format: $VERSION (expected X.Y.Z)"
26
+ exit 1
27
+ fi
28
+
29
+ - uses: astral-sh/setup-uv@v5
30
+
31
+ - name: Install project dependencies
32
+ run: uv sync --extra dev
33
+
34
+ - name: Check version is newer than current
35
+ run: |
36
+ VERSION="${{ inputs.version }}"
37
+ CURRENT=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
38
+ echo "Current version: $CURRENT"
39
+ echo "Target version: $VERSION"
40
+ uv run python -c "
41
+ from packaging.version import Version
42
+ current = Version('$CURRENT')
43
+ target = Version('$VERSION')
44
+ if target <= current:
45
+ raise SystemExit(f'Target {target} must be newer than current {current}')
46
+ print(f'OK: {current} → {target}')
47
+ "
48
+
49
+ - name: Check no existing tag
50
+ run: |
51
+ VERSION="${{ inputs.version }}"
52
+ if git ls-remote --tags origin "refs/tags/v${VERSION}" | grep -q .; then
53
+ echo "::error::Tag v${VERSION} already exists"
54
+ exit 1
55
+ fi
56
+
57
+ - name: Create or reset release branch
58
+ run: |
59
+ VERSION="${{ inputs.version }}"
60
+ BRANCH="release/v${VERSION}"
61
+ if git ls-remote --heads origin "refs/heads/${BRANCH}" | grep -q .; then
62
+ echo "Branch ${BRANCH} already exists — will overwrite"
63
+ fi
64
+ git checkout -b "$BRANCH"
65
+
66
+ - name: Bump version in pyproject.toml
67
+ run: |
68
+ VERSION="${{ inputs.version }}"
69
+ sed -i "s/^version = \".*\"/version = \"${VERSION}\"/" pyproject.toml
70
+ grep "^version = \"${VERSION}\"" pyproject.toml
71
+
72
+ - name: Build changelog with towncrier
73
+ run: |
74
+ VERSION="${{ inputs.version }}"
75
+ echo "=== Changelog preview ==="
76
+ uv run towncrier build --draft --version "$VERSION"
77
+ echo "========================="
78
+ uv run towncrier build --version "$VERSION" --yes
79
+ echo "--- Updated CHANGELOG.md (first 30 lines) ---"
80
+ head -30 CHANGELOG.md
81
+
82
+ - name: Commit and push
83
+ env:
84
+ GH_TOKEN: ${{ secrets.RELEASE_PAT }}
85
+ run: |
86
+ VERSION="${{ inputs.version }}"
87
+ BRANCH="release/v${VERSION}"
88
+ git config user.name "github-actions[bot]"
89
+ git config user.email "github-actions[bot]@users.noreply.github.com"
90
+ git add pyproject.toml CHANGELOG.md changes/
91
+ git commit -m "Release v${VERSION}"
92
+ git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${{ github.repository }}.git"
93
+ git push --force origin "$BRANCH"
94
+
95
+ - name: Create or update pull request
96
+ env:
97
+ GH_TOKEN: ${{ secrets.RELEASE_PAT }}
98
+ run: |
99
+ VERSION="${{ inputs.version }}"
100
+ BRANCH="release/v${VERSION}"
101
+
102
+ CHANGELOG_BODY=$(awk "/^## ${VERSION//./\\.} /{found=1; next} /^## /{if(found) exit} found{print}" CHANGELOG.md)
103
+
104
+ PR_BODY="$(cat <<EOF
105
+ ## Release v${VERSION}
106
+
107
+ ${CHANGELOG_BODY}
108
+
109
+ ---
110
+ **Merge this PR to trigger the release pipeline.**
111
+ The release workflow will auto-tag \`v${VERSION}\`, build all artifacts, validate on TestPyPI, then publish to PyPI.
112
+ EOF
113
+ )"
114
+
115
+ EXISTING_PR=$(gh pr list --head "$BRANCH" --json number --jq '.[0].number' 2>/dev/null || echo "")
116
+
117
+ if [[ -n "$EXISTING_PR" ]]; then
118
+ echo "Updating existing PR #${EXISTING_PR}"
119
+ gh pr edit "$EXISTING_PR" \
120
+ --title "Release v${VERSION}" \
121
+ --body "$PR_BODY"
122
+ else
123
+ gh pr create \
124
+ --title "Release v${VERSION}" \
125
+ --body "$PR_BODY" \
126
+ --base main \
127
+ --head "$BRANCH"
128
+ fi
@@ -0,0 +1,218 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+ inputs:
8
+ tag:
9
+ description: "Release tag (e.g. v0.2.0) — use only as fallback"
10
+ required: true
11
+
12
+ jobs:
13
+ # ── Detect release PR merge on push to main ─────────────────────────
14
+ detect-release:
15
+ if: github.event_name == 'push'
16
+ runs-on: ubuntu-latest
17
+ outputs:
18
+ is_release: ${{ steps.check.outputs.is_release }}
19
+ version: ${{ steps.check.outputs.version }}
20
+ steps:
21
+ - name: Check if this is a release PR merge
22
+ id: check
23
+ env:
24
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
25
+ run: |
26
+ SHA="${{ github.sha }}"
27
+ echo "Checking commit $SHA for associated release PR..."
28
+
29
+ BRANCHES=$(gh api "repos/${{ github.repository }}/commits/${SHA}/pulls" \
30
+ --jq '.[].head.ref')
31
+ echo "Associated PR branches: ${BRANCHES:-<none>}"
32
+
33
+ VERSION=$(echo "$BRANCHES" \
34
+ | grep -oP '^release/v\K\d+\.\d+\.\d+$' \
35
+ | head -1 || true)
36
+
37
+ if [[ -n "$VERSION" ]]; then
38
+ if gh api "repos/${{ github.repository }}/git/refs/tags/v${VERSION}" >/dev/null 2>&1; then
39
+ echo "::warning::Tag v${VERSION} already exists — skipping"
40
+ echo "is_release=false" >> "$GITHUB_OUTPUT"
41
+ else
42
+ echo "is_release=true" >> "$GITHUB_OUTPUT"
43
+ echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
44
+ echo "::notice::Detected release PR merge for v${VERSION}"
45
+ fi
46
+ else
47
+ echo "is_release=false" >> "$GITHUB_OUTPUT"
48
+ fi
49
+
50
+ # ── Verify version ──────────────────────────────────────────────────
51
+ version:
52
+ needs: detect-release
53
+ if: |
54
+ always() &&
55
+ ((needs.detect-release.outputs.is_release == 'true') ||
56
+ github.event_name == 'workflow_dispatch')
57
+ runs-on: ubuntu-latest
58
+ outputs:
59
+ version: ${{ steps.version.outputs.version }}
60
+ steps:
61
+ - uses: actions/checkout@v6
62
+ with:
63
+ ref: main
64
+
65
+ - name: Extract and verify version
66
+ id: version
67
+ run: |
68
+ if [[ "${{ github.event_name }}" == "push" ]]; then
69
+ VERSION="${{ needs.detect-release.outputs.version }}"
70
+ else
71
+ TAG="${{ inputs.tag }}"
72
+ VERSION="${TAG#v}"
73
+ fi
74
+ echo "version=$VERSION" >> "$GITHUB_OUTPUT"
75
+
76
+ TOML_VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
77
+ if [[ "$TOML_VERSION" != "$VERSION" ]]; then
78
+ echo "::error::Version mismatch: pyproject.toml=$TOML_VERSION, release=$VERSION"
79
+ exit 1
80
+ fi
81
+ echo "::notice::Releasing version $VERSION"
82
+
83
+ # ── Build package ───────────────────────────────────────────────────
84
+ build-pypi:
85
+ needs: version
86
+ if: always() && needs.version.result == 'success'
87
+ runs-on: ubuntu-latest
88
+ steps:
89
+ - uses: actions/checkout@v6
90
+ with:
91
+ ref: main
92
+
93
+ - name: Set up Python
94
+ uses: actions/setup-python@v6
95
+ with:
96
+ python-version: "3.11"
97
+
98
+ - name: Build and validate package
99
+ run: |
100
+ pip install build twine
101
+ python -m build
102
+ twine check dist/*
103
+
104
+ - uses: actions/upload-artifact@v7
105
+ with:
106
+ name: pypi-dist
107
+ path: dist/
108
+ retention-days: 5
109
+
110
+ # ── Dry-run: validate on TestPyPI before real publish ───────────────
111
+ dry-run-publish:
112
+ needs: [version, build-pypi]
113
+ if: always() && needs.version.result == 'success' && needs.build-pypi.result == 'success'
114
+ runs-on: ubuntu-latest
115
+ environment: testpypi
116
+ permissions:
117
+ id-token: write
118
+ steps:
119
+ - uses: actions/download-artifact@v7
120
+ with:
121
+ name: pypi-dist
122
+ path: dist/
123
+
124
+ - name: Publish to TestPyPI (dry-run gate)
125
+ uses: pypa/gh-action-pypi-publish@release/v1
126
+ with:
127
+ repository-url: https://test.pypi.org/legacy/
128
+ skip-existing: true
129
+
130
+ # ── Create tag only after TestPyPI succeeds ─────────────────────────
131
+ create-tag:
132
+ needs: [version, detect-release, dry-run-publish]
133
+ if: |
134
+ always() &&
135
+ needs.version.result == 'success' &&
136
+ needs.dry-run-publish.result == 'success'
137
+ runs-on: ubuntu-latest
138
+ permissions:
139
+ contents: write
140
+ env:
141
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
142
+ steps:
143
+ - uses: actions/checkout@v6
144
+ with:
145
+ ref: main
146
+
147
+ - name: Create and push tag (if it doesn't exist)
148
+ run: |
149
+ VERSION="${{ needs.version.outputs.version }}"
150
+ if gh api "repos/${{ github.repository }}/git/refs/tags/v${VERSION}" >/dev/null 2>&1; then
151
+ echo "::notice::Tag v${VERSION} already exists — skipping"
152
+ else
153
+ git tag "v${VERSION}"
154
+ git push origin "v${VERSION}"
155
+ echo "::notice::Created tag v${VERSION}"
156
+ fi
157
+
158
+ # ── Publish to PyPI ─────────────────────────────────────────────────
159
+ publish-pypi:
160
+ needs: [version, create-tag, dry-run-publish]
161
+ if: |
162
+ always() &&
163
+ needs.dry-run-publish.result == 'success' &&
164
+ (needs.create-tag.result == 'success' || needs.create-tag.result == 'skipped')
165
+ runs-on: ubuntu-latest
166
+ permissions:
167
+ id-token: write
168
+ steps:
169
+ - uses: actions/download-artifact@v7
170
+ with:
171
+ name: pypi-dist
172
+ path: dist/
173
+
174
+ - name: Publish to PyPI
175
+ uses: pypa/gh-action-pypi-publish@release/v1
176
+ with:
177
+ skip-existing: true
178
+
179
+ # ── Create GitHub Release ───────────────────────────────────────────
180
+ github-release:
181
+ needs: [version, publish-pypi]
182
+ if: |
183
+ always() &&
184
+ needs.version.result == 'success' &&
185
+ needs.publish-pypi.result == 'success'
186
+ runs-on: ubuntu-latest
187
+ permissions:
188
+ contents: write
189
+ steps:
190
+ - uses: actions/checkout@v6
191
+ with:
192
+ ref: main
193
+
194
+ - uses: actions/download-artifact@v7
195
+ with:
196
+ name: pypi-dist
197
+ path: dist/
198
+
199
+ - name: Extract changelog for this version
200
+ id: changelog
201
+ run: |
202
+ VERSION="${{ needs.version.outputs.version }}"
203
+ BODY=$(awk "/^## ${VERSION//./\\.} /{found=1; next} /^## /{if(found) exit} found{print}" CHANGELOG.md)
204
+ echo "$BODY" > /tmp/release-body.md
205
+ echo "Changelog body:"
206
+ cat /tmp/release-body.md
207
+
208
+ - name: Create GitHub Release
209
+ env:
210
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
211
+ run: |
212
+ VERSION="${{ needs.version.outputs.version }}"
213
+ WHEEL=$(ls dist/*.whl 2>/dev/null | head -1)
214
+ ARGS=(gh release create "v${VERSION}" --title "v${VERSION}" --notes-file /tmp/release-body.md)
215
+ if [[ -n "$WHEEL" ]]; then
216
+ ARGS+=("$WHEEL")
217
+ fi
218
+ "${ARGS[@]}"
@@ -0,0 +1,58 @@
1
+ name: Publish to TestPyPI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+
8
+ jobs:
9
+ test-publish:
10
+ runs-on: ubuntu-latest
11
+ environment: testpypi
12
+ permissions:
13
+ id-token: write
14
+ steps:
15
+ - uses: actions/checkout@v6
16
+ with:
17
+ fetch-depth: 0
18
+
19
+ - name: Skip if this is a release PR merge
20
+ id: release
21
+ if: github.event_name == 'push'
22
+ env:
23
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24
+ run: |
25
+ BRANCH=$(gh api "repos/${{ github.repository }}/commits/${{ github.sha }}/pulls" \
26
+ --jq '.[0].head.ref' 2>/dev/null || echo "")
27
+ if [[ -z "$BRANCH" ]]; then
28
+ echo "::warning::Could not determine PR branch — proceeding"
29
+ echo "skip=false" >> "$GITHUB_OUTPUT"
30
+ elif [[ "$BRANCH" =~ ^release/v ]]; then
31
+ echo "::notice::Release PR merge — skipping (release.yml handles publishing)"
32
+ echo "skip=true" >> "$GITHUB_OUTPUT"
33
+ else
34
+ echo "skip=false" >> "$GITHUB_OUTPUT"
35
+ fi
36
+
37
+ - name: Set up Python
38
+ if: steps.release.outputs.skip != 'true'
39
+ uses: actions/setup-python@v6
40
+ with:
41
+ python-version: "3.11"
42
+
43
+ - name: Build dev package for TestPyPI
44
+ if: steps.release.outputs.skip != 'true'
45
+ run: |
46
+ pip install build
47
+ VERSION=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
48
+ COMMIT_COUNT=$(git rev-list --count HEAD)
49
+ DEV_VERSION="${VERSION}.dev${COMMIT_COUNT}"
50
+ sed -i "s/version = \"$VERSION\"/version = \"$DEV_VERSION\"/" pyproject.toml
51
+ python -m build
52
+
53
+ - name: Publish to TestPyPI
54
+ if: steps.release.outputs.skip != 'true'
55
+ uses: pypa/gh-action-pypi-publish@release/v1
56
+ with:
57
+ repository-url: https://test.pypi.org/legacy/
58
+ skip-existing: true
@@ -0,0 +1,6 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.egg-info/
4
+ dist/
5
+ build/
6
+ .pytest_cache/
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to bambox are documented here.
4
+ This changelog is managed by [towncrier](https://towncrier.readthedocs.io/).
5
+
6
+ <!-- towncrier release notes start -->
7
+
8
+ ## 0.1.0 — 2026-03-15
9
+
10
+ Initial release of bambox as a standalone library.
11
+
12
+ ### Features
13
+
14
+ - Core `.gcode.3mf` archive packaging with Bambu Connect compatibility
15
+ - Template-driven 544-key `project_settings.config` generation from JSON profiles
16
+ - Machine base profile for P1S with filament overlays (PLA, ASA, PETG-CF)
17
+ - Automatic array padding and missing-key fixup for Bambu Connect firmware
18
+ - Cloud printing via Docker bridge (`estampo/cloud-bridge`) with bind-mount and baked fallback
19
+ - AMS tray mapping and printer status querying
20
+ - OrcaSlicer-to-Jinja2 template syntax conversion and rendering
21
+ - G-code component assembly (start + toolpath + end templates)
22
+ - CuraEngine Docker slicer backend prototype
23
+ - Synthetic toolpath generator for testing
24
+ - G-code-to-PNG thumbnail rendering (top-down view with bounding box)
25
+ - CLI with `pack`, `print`, and `status` commands
26
+ - MD5 checksum validation matching Bambu Connect requirements
27
+ - Support for both OrcaSlicer 2.3.1 and BambuStudio 2.5.0.66 format versions
bambox-0.1.0/CLAUDE.md ADDED
@@ -0,0 +1,74 @@
1
+ # bambox - Claude Code Instructions
2
+
3
+ ## Working with the maintainer
4
+ The project maintainer is technically experienced and understands the codebase deeply. Trust their judgement. Don't second-guess their observations or explain things they already know.
5
+
6
+ ## Pre-PR Checklist (MANDATORY)
7
+ Before pushing any PR branch, always run locally:
8
+ 1. `uv run ruff check src tests` — lint must pass with zero errors
9
+ 2. `uv run ruff format --check src tests` — formatting must pass (run `uv run ruff format src tests` to auto-fix)
10
+ 3. `uv run mypy src/bambox` — type check must pass with zero errors
11
+ 4. `uv run pytest` — all tests must pass
12
+
13
+ Do NOT push a PR until all four checks pass locally.
14
+
15
+ ## Changelog (MANDATORY)
16
+ Every PR must include a **towncrier fragment file** in the `changes/` directory:
17
+ 1. Create a file: `changes/<PR-number>.<type>` where type is `feature`, `bugfix`, or `misc`
18
+ 2. Write a single line — concise, user-facing description of the change
19
+ 3. If the PR has no number yet, use `+descriptive-name.<type>` (orphan fragment)
20
+ 4. Do NOT edit CHANGELOG.md directly — towncrier compiles fragments at release time
21
+
22
+ ## Module Ownership (enforce strictly)
23
+
24
+ Each module has a defined scope. Do not add logic to the wrong module — even if it seems convenient.
25
+
26
+ | Module | Owns | Must NOT contain |
27
+ |--------|------|-----------------|
28
+ | `pack.py` | Core .gcode.3mf archive construction, XML metadata, MD5 checksums, Bambu Connect fixup | Settings generation, slicer logic, printer communication |
29
+ | `settings.py` | 544-key project_settings builder, profile loading, filament overlay, array broadcasting | G-code generation, archive packing, printer logic |
30
+ | `bridge.py` | Cloud printing via Docker bridge, credential loading, AMS tray mapping, printer status | Archive construction, settings generation, slicer invocation |
31
+ | `cli.py` | Typer commands (pack, print, status), argument parsing, user-facing output | Business logic — delegate to pack/bridge/settings |
32
+ | `cura.py` | CuraEngine Docker invocation, profile conversion, start/end G-code injection | OrcaSlicer logic, archive packing, printer communication |
33
+ | `templates.py` | OrcaSlicer→Jinja2 syntax conversion, template rendering | G-code generation, settings logic |
34
+ | `toolpath.py` | Synthetic toolpath generation for testing | Production G-code, slicer invocation |
35
+ | `thumbnail.py` | G-code→PNG rendering (top-down view, bounding box) | Archive packing, settings |
36
+ | `assemble.py` | G-code component assembly (start + toolpath + end) | Slicer invocation, template rendering |
37
+
38
+ ## Architecture: Key Decisions
39
+
40
+ ### Template-Driven Settings (544 keys)
41
+ Bambu printers require a `project_settings.config` with ~544 keys in the .gcode.3mf archive. Rather than passing slicer output through, bambox builds this from:
42
+ 1. Machine base profile (e.g. `base_p1s.json` — 544 keys)
43
+ 2. Filament type profiles (`filament_pla.json`, etc. — per-type overrides)
44
+ 3. `_varying_keys.json` — keys that differ per filament slot
45
+ 4. `_uniform_array_keys.json` — scalars that must be broadcast to arrays
46
+
47
+ Do NOT modify `fixup_project_settings()` without understanding the array padding and key fixup logic — it ensures Bambu Connect firmware acceptance.
48
+
49
+ ### Docker Bridge Pattern
50
+ Cloud printing wraps `estampo/cloud-bridge:bambu-*` Docker image. Two modes:
51
+ 1. **Bind-mount** (primary) — mounts .gcode.3mf via `-v`
52
+ 2. **Baked fallback** — for sandboxed/DinD environments, builds temp image with COPY
53
+
54
+ ### Bambu Connect Compatibility
55
+ The archive format is validated by printer firmware. Key constraints:
56
+ - MD5 checksums must match file contents
57
+ - Per-filament arrays must be padded to exactly 5 slots (P1S)
58
+ - Both OrcaSlicer 2.3.1 and BambuStudio 2.5.0.66 format versions supported
59
+
60
+ ## What bambox is NOT
61
+
62
+ - **Not a slicer.** It packages G-code produced by slicers. The CuraEngine integration in `cura.py` invokes an external engine — it does not implement slicing.
63
+ - **Not a printer API client.** It wraps the Docker bridge for cloud printing. The actual protocol implementation lives in the bridge binary (C++ today, Rust planned).
64
+ - **Not estampo.** estampo is the pipeline orchestrator. bambox is the Bambu Lab packaging library that estampo depends on. Do not add pipeline, DAG, or orchestration logic here.
65
+ - **Not a profile editor.** It loads and overlays profiles. Do not build profile editing or merging UI.
66
+
67
+ ## Relationship to estampo
68
+
69
+ bambox is one piece of a three-project architecture:
70
+ - **estampo** — pipeline orchestrator, slicer-agnostic
71
+ - **bambox** — BBL .gcode.3mf packaging + G-code templates + settings generation
72
+ - **bambu-cloud** (planned) — printer communication
73
+
74
+ Per estampo ADR-005, bambox will absorb printer code from estampo at v0.4.0. See `docs/bridge-migration-plan.md` for the Rust bridge migration plan.
@@ -0,0 +1,57 @@
1
+ # Contributing to bambox
2
+
3
+ ## Development setup
4
+
5
+ ```bash
6
+ git clone https://github.com/estampo/bambox.git
7
+ cd bambox
8
+ uv sync --extra dev
9
+ ```
10
+
11
+ ## Before submitting a PR
12
+
13
+ Run all four checks locally:
14
+
15
+ ```bash
16
+ uv run ruff check src tests # lint
17
+ uv run ruff format --check src tests # formatting
18
+ uv run mypy src/bambox # type check
19
+ uv run pytest # tests
20
+ ```
21
+
22
+ Auto-fix formatting: `uv run ruff format src tests`
23
+
24
+ ## Changelog fragments
25
+
26
+ Every PR must include a towncrier fragment file in `changes/`:
27
+
28
+ ```bash
29
+ # Format: changes/<PR-number>.<type>
30
+ # Types: feature, bugfix, misc
31
+ echo "Add support for A1 Mini base profile" > changes/42.feature
32
+ ```
33
+
34
+ If you don't have a PR number yet, use an orphan fragment:
35
+
36
+ ```bash
37
+ echo "Fix array padding for single-filament prints" > changes/+fix-padding.bugfix
38
+ ```
39
+
40
+ Do **not** edit `CHANGELOG.md` directly.
41
+
42
+ ## Code style
43
+
44
+ - Line length: 100 characters (ruff)
45
+ - Type hints on all public functions
46
+ - `from __future__ import annotations` in every module
47
+ - No unnecessary comments or docstrings on private helpers
48
+
49
+ ## Module boundaries
50
+
51
+ See `CLAUDE.md` for the module ownership table. Each module has a defined scope — don't add logic to the wrong module.
52
+
53
+ ## Tests
54
+
55
+ - Tests live in `tests/` and mirror source module names
56
+ - Use `tmp_path` fixture for file I/O tests
57
+ - Reference fixtures (`tests/fixtures/`) are Bambu Connect-validated archives