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.
- bambox-0.1.0/.github/workflows/ci.yml +68 -0
- bambox-0.1.0/.github/workflows/prepare-release.yml +128 -0
- bambox-0.1.0/.github/workflows/release.yml +218 -0
- bambox-0.1.0/.github/workflows/test-pypi.yml +58 -0
- bambox-0.1.0/.gitignore +6 -0
- bambox-0.1.0/CHANGELOG.md +27 -0
- bambox-0.1.0/CLAUDE.md +74 -0
- bambox-0.1.0/CONTRIBUTING.md +57 -0
- bambox-0.1.0/PKG-INFO +182 -0
- bambox-0.1.0/README.md +161 -0
- bambox-0.1.0/ROADMAP.md +146 -0
- bambox-0.1.0/SECURITY.md +26 -0
- bambox-0.1.0/bridge/.gitignore +1 -0
- bambox-0.1.0/bridge/Cargo.lock +2344 -0
- bambox-0.1.0/bridge/Cargo.toml +28 -0
- bambox-0.1.0/bridge/build.rs +13 -0
- bambox-0.1.0/bridge/shim/shim.cpp +519 -0
- bambox-0.1.0/bridge/src/agent.rs +753 -0
- bambox-0.1.0/bridge/src/callbacks.rs +206 -0
- bambox-0.1.0/bridge/src/ffi.rs +150 -0
- bambox-0.1.0/bridge/src/main.rs +321 -0
- bambox-0.1.0/bridge/src/print_job.rs +574 -0
- bambox-0.1.0/bridge/src/server.rs +629 -0
- bambox-0.1.0/changes/+align-ruff-config.misc +1 -0
- bambox-0.1.0/changes/+fix-testpypi-trigger.misc +1 -0
- bambox-0.1.0/changes/+remove-dead-cura.misc +1 -0
- bambox-0.1.0/changes/.gitkeep +0 -0
- bambox-0.1.0/decoy_base.gcode.3mf +0 -0
- bambox-0.1.0/docs/daemon-bridge-design.md +213 -0
- bambox-0.1.0/docs/decisions/001-template-driven-settings.md +53 -0
- bambox-0.1.0/pyproject.toml +70 -0
- bambox-0.1.0/src/bambox/__init__.py +24 -0
- bambox-0.1.0/src/bambox/assemble.py +47 -0
- bambox-0.1.0/src/bambox/bridge.py +513 -0
- bambox-0.1.0/src/bambox/cli.py +233 -0
- bambox-0.1.0/src/bambox/gcode_compat.py +221 -0
- bambox-0.1.0/src/bambox/gcode_templates/p1s_end.gcode.j2 +54 -0
- bambox-0.1.0/src/bambox/gcode_templates/p1s_start.gcode.j2 +269 -0
- bambox-0.1.0/src/bambox/gcode_templates/p1s_toolchange.gcode.j2 +188 -0
- bambox-0.1.0/src/bambox/input_spec.example.json +65 -0
- bambox-0.1.0/src/bambox/pack.py +450 -0
- bambox-0.1.0/src/bambox/profiles/_uniform_array_keys.json +111 -0
- bambox-0.1.0/src/bambox/profiles/_varying_keys.json +41 -0
- bambox-0.1.0/src/bambox/profiles/base_p1s.json +569 -0
- bambox-0.1.0/src/bambox/profiles/filament_asa.json +41 -0
- bambox-0.1.0/src/bambox/profiles/filament_petg_cf.json +41 -0
- bambox-0.1.0/src/bambox/profiles/filament_pla.json +41 -0
- bambox-0.1.0/src/bambox/settings.py +171 -0
- bambox-0.1.0/src/bambox/templates.py +142 -0
- bambox-0.1.0/src/bambox/thumbnail.py +112 -0
- bambox-0.1.0/src/bambox/toolpath.py +211 -0
- bambox-0.1.0/test-cube-petgcf.gcode.3mf +0 -0
- bambox-0.1.0/test_box_10x10x5.gcode.3mf +0 -0
- bambox-0.1.0/tests/__init__.py +0 -0
- bambox-0.1.0/tests/fixtures/plate_1.png +0 -0
- bambox-0.1.0/tests/fixtures/plate_1_small.png +0 -0
- bambox-0.1.0/tests/fixtures/plate_no_light_1.png +0 -0
- bambox-0.1.0/tests/fixtures/project_settings.json +1496 -0
- bambox-0.1.0/tests/fixtures/reference.gcode.3mf +0 -0
- bambox-0.1.0/tests/test_e2e.py +248 -0
- bambox-0.1.0/tests/test_gcode_compat.py +224 -0
- bambox-0.1.0/tests/test_pack.py +334 -0
- bambox-0.1.0/tests/test_templates.py +219 -0
- 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
|
bambox-0.1.0/.gitignore
ADDED
|
@@ -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
|