Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
a5f92e4
Make all settings sections collapsible
rtwfroody Apr 28, 2026
748413b
Show "(including alpha-wrap)" on Loading stage when wrap is on
rtwfroody Apr 29, 2026
b0f7714
Begin per-stage progress only after prerequisites resolve
rtwfroody Apr 29, 2026
971b6cb
Rebalance disk-cache eviction toward absolute generation cost
rtwfroody Apr 29, 2026
e2d6c63
Drop in-memory stage cache; rely on disk + OS page cache
rtwfroody Apr 29, 2026
834c17f
Fix within-run get-after-set race; extract runStage helper
rtwfroody Apr 29, 2026
5f727a7
Add split-feature design doc and phase 1 geometry primitive
rtwfroody Apr 29, 2026
17792df
Add split phase 2: connector placement and peg/pocket geometry
rtwfroody Apr 29, 2026
4d99f38
Apply phase 2 review fixes
rtwfroody Apr 29, 2026
9718566
Drop tilted-plane connector test (out of v1 scope)
rtwfroody Apr 29, 2026
b9f838c
Add split phase 3: layout (cap-down + side-by-side on bed)
rtwfroody Apr 29, 2026
8dbff06
Apply phase 3 review fixes
rtwfroody Apr 29, 2026
694245f
Add split phase 4: voxelize signature with optional SplitInfo
rtwfroody Apr 29, 2026
f8b15b3
Apply phase 4 review fixes
rtwfroody Apr 29, 2026
c8677c3
Add split phase 5: per-half decimation glue
rtwfroody Apr 29, 2026
812c874
Apply phase 5 review fixes
rtwfroody Apr 29, 2026
0a3306b
Add split phase 6: StageSplit pipeline integration
rtwfroody Apr 29, 2026
0701ac5
Apply phase 6 review fixes
rtwfroody Apr 29, 2026
083b651
Add split phase 7: split-aware Clip + Merge plumbing
rtwfroody Apr 29, 2026
d918fa3
Apply phase 7 review fixes
rtwfroody Apr 30, 2026
e004f09
Add split phase 7 follow-up: 3MF multi-object emission (non-bambu)
rtwfroody Apr 30, 2026
ef58c2d
Add split phase 8: SplitPreview Wails method for cut-plane overlay
rtwfroody Apr 30, 2026
8e48f99
Apply phase 8 review fixes
rtwfroody Apr 30, 2026
1850d1b
Add split phase 9: frontend Split section + AlphaWrap coupling
rtwfroody Apr 30, 2026
ebc5881
Apply phase 9 review fixes
rtwfroody Apr 30, 2026
f08b5e6
Add live cut-plane preview overlay and clarify Split UI
rtwfroody Apr 30, 2026
d9cd2a5
Bump version to 0.7.0
rtwfroody Apr 30, 2026
b934e23
Fix cut-plane $effect self-tracking infinite loop
rtwfroody Apr 30, 2026
d86eaf2
Persist Split panel state in saved settings JSON
rtwfroody Apr 30, 2026
99e2f7e
Show pipeline errors as a final line in the output stage list
rtwfroody Apr 30, 2026
d7ec1a8
Rescue cut planes that pass through model vertices
rtwfroody Apr 30, 2026
8427d35
Add timestamped pipeline logging plus cache hit/miss visibility
rtwfroody Apr 30, 2026
af7929a
Show cache key in eviction and disk-cache error logs
rtwfroody Apr 30, 2026
bb5036f
Tune cache eviction so freshly-generated entries dominate
rtwfroody Apr 30, 2026
cbc3635
Switch recency decay to power-law
rtwfroody Apr 30, 2026
9e75092
Snap on-plane vertices off the cut plane instead of perturbing
rtwfroody Apr 30, 2026
3e8aa3f
Log start and end of every pipeline stage uniformly
rtwfroody Apr 30, 2026
5ae2498
Support multi-component cap polygons
rtwfroody Apr 30, 2026
19b8cdf
Drop ReloadSeq from the parse stage cache key
rtwfroody Apr 30, 2026
b72b8df
Make earClip robust against near-collinear cap polygons
rtwfroody Apr 30, 2026
7be9556
Skip degenerate cap regions instead of aborting the cut
rtwfroody Apr 30, 2026
466a06b
Show entry age in evict log; close caps with fan fallback
rtwfroody Apr 30, 2026
4ddbfc5
Preserve holes in fan-fallback caps as inverted patches
rtwfroody Apr 30, 2026
a89a59f
Replace hand-rolled cut with CGAL Polygon_mesh_processing::clip
rtwfroody Apr 30, 2026
e050137
Apply review fixes for CGAL cut switch
rtwfroody Apr 30, 2026
979f67a
Drop the cgal build tag
rtwfroody Apr 30, 2026
3a838c0
Give each inner 3MF .model a unique object id
rtwfroody Apr 30, 2026
539574c
Wait for async disk writes before reading them in ExportFile
rtwfroody May 1, 2026
22c81f9
Re-implement Split connectors via CGAL booleans
rtwfroody May 1, 2026
87ccab7
Treat Split connector count 0 as auto-default, not disabled
rtwfroody May 1, 2026
9eaea86
Keep Split pegs away from cap polygon boundary
rtwfroody May 1, 2026
0c622cf
Print male-peg half cap-up so pegs point upward
rtwfroody May 1, 2026
24450ae
Document Split feature and other recent UX changes in README
rtwfroody May 1, 2026
cfcf180
Drop linux/arm64 and darwin/amd64 from release CI
rtwfroody May 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 17 additions & 53 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,14 @@ jobs:
- os: ubuntu-latest
platform: linux-amd64
wails_flags: "-tags webkit2_41,draco,cgal"
cli_targets: "linux/amd64 linux/arm64"
cgal_cli_targets: "linux/amd64"
- os: windows-latest
platform: windows-amd64
wails_flags: "-tags draco,cgal"
cli_targets: "windows/amd64"
cgal_cli_targets: "windows/amd64"
- os: macos-latest
platform: macos-universal
platform: macos-arm64
wails_flags: ""
cli_targets: "darwin/amd64 darwin/arm64"
cgal_cli_targets: "darwin/arm64"

runs-on: ${{ matrix.os }}
Expand Down Expand Up @@ -111,23 +108,15 @@ jobs:
shell: bash
run: wails build ${{ matrix.wails_flags }}

- name: Build GUI (macOS universal with CGAL on arm64)
- name: Build GUI (macOS arm64 with CGAL)
if: runner.os == 'macOS'
shell: bash
run: |
# Build arm64 with CGAL and Draco linked in.
# arm64-only build. Intel Macs are not supported by the release
# pipeline since CGAL is required at build time and no
# cross-compiled GMP/MPFR is available on the macOS arm64 runner.
# customenv tells draco-go to use our system libdraco_c instead of its bundled one.
CGO_LDFLAGS="-L/usr/local/lib -ldraco_c -lstdc++ -lm" wails build -platform darwin/arm64 -tags customenv,draco,cgal
cp build/bin/ditherforge.app/Contents/MacOS/ditherforge ditherforge-gui-arm64

# Build amd64 without CGAL (no cross-compiled GMP/MPFR available).
wails build -platform darwin/amd64
cp build/bin/ditherforge.app/Contents/MacOS/ditherforge ditherforge-gui-amd64

# Combine into universal binary inside the .app bundle.
lipo -create -output build/bin/ditherforge.app/Contents/MacOS/ditherforge \
ditherforge-gui-arm64 ditherforge-gui-amd64
rm ditherforge-gui-arm64 ditherforge-gui-amd64

# Verify no Homebrew dylib dependencies leaked into the binary.
if otool -L build/bin/ditherforge.app/Contents/MacOS/ditherforge | grep -q /opt/homebrew; then
Expand Down Expand Up @@ -158,31 +147,12 @@ jobs:
go build -tags "$TAGS" -o "ditherforge-cli-${GOOS}-${GOARCH}${EXT}" ./cmd/ditherforge
done

- name: Build CLI (without CGAL, cross-compile)
shell: bash
run: |
for target in ${{ matrix.cli_targets }}; do
GOOS="${target%/*}"
GOARCH="${target#*/}"
EXT=""
if [ "$GOOS" = "windows" ]; then EXT=".exe"; fi
OUT="ditherforge-cli-${GOOS}-${GOARCH}${EXT}"
if [ -f "$OUT" ]; then
echo "Skipping ${GOOS}/${GOARCH} (already built with CGAL)"
continue
fi
echo "Building CLI for ${GOOS}/${GOARCH} (no CGAL)..."
CGO_ENABLED=0 GOOS="$GOOS" GOARCH="$GOARCH" \
go build -o "$OUT" ./cmd/ditherforge
done

# --- Smoke test (Linux only) ---

- name: Smoke test
if: runner.os == 'Linux'
run: |
./ditherforge-cli-linux-amd64 --version
go test -tags cgal -timeout 2m ./internal/alphawrap/...
go test -timeout 2m ./internal/...

# --- Package ---
Expand All @@ -199,14 +169,12 @@ jobs:
tar czf "ditherforge-${VERSION}-linux-amd64.tar.gz" -C pkg .
rm -rf pkg

# CLI: tar.gz for each arch
for arch in amd64 arm64; do
mkdir -p pkg
cp "ditherforge-cli-linux-${arch}" pkg/ditherforge-cli
cp README.md LICENSE pkg/
tar czf "ditherforge-cli-${VERSION}-linux-${arch}.tar.gz" -C pkg .
rm -rf pkg
done
# CLI: tar.gz
mkdir -p pkg
cp ditherforge-cli-linux-amd64 pkg/ditherforge-cli
cp README.md LICENSE pkg/
tar czf "ditherforge-cli-${VERSION}-linux-amd64.tar.gz" -C pkg .
rm -rf pkg

- name: Package (Windows)
if: runner.os == 'Windows'
Expand Down Expand Up @@ -240,17 +208,14 @@ jobs:
hdiutil create -volname "DitherForge" \
-srcfolder dmg-staging \
-ov -format UDZO \
"ditherforge-${VERSION}-macos-universal.dmg"
"ditherforge-${VERSION}-macos-arm64.dmg"
rm -rf dmg-staging

# CLI: universal binary via lipo, sign, then tar.gz
lipo -create -output ditherforge-cli \
ditherforge-cli-darwin-amd64 \
ditherforge-cli-darwin-arm64
# CLI: arm64 only
mkdir -p pkg
cp ditherforge-cli pkg/
cp ditherforge-cli-darwin-arm64 pkg/ditherforge-cli
cp README.md LICENSE pkg/
tar czf "ditherforge-cli-${VERSION}-macos-universal.tar.gz" -C pkg .
tar czf "ditherforge-cli-${VERSION}-macos-arm64.tar.gz" -C pkg .
rm -rf pkg

- uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -326,7 +291,7 @@ jobs:

- Linux: \`ditherforge-${VERSION}-linux-amd64.tar.gz\`
- Windows: \`ditherforge-${VERSION}-windows-amd64.zip\`
- macOS: \`ditherforge-${VERSION}-macos-universal.dmg\`
- macOS (Apple Silicon): \`ditherforge-${VERSION}-macos-arm64.dmg\`

For the command-line build, see the **Latest CLI build** release.
EOF
Expand All @@ -346,9 +311,8 @@ jobs:
## Downloads

- Linux amd64: \`ditherforge-cli-${VERSION}-linux-amd64.tar.gz\`
- Linux arm64: \`ditherforge-cli-${VERSION}-linux-arm64.tar.gz\`
- Windows: \`ditherforge-cli-${VERSION}-windows-amd64.zip\`
- macOS universal: \`ditherforge-cli-${VERSION}-macos-universal.tar.gz\`
- macOS (Apple Silicon): \`ditherforge-cli-${VERSION}-macos-arm64.tar.gz\`

For the desktop app, see the **Latest GUI build** release.
EOF
Expand Down
113 changes: 96 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ Pre-built binaries for Linux, Windows, and macOS are available on the
3. Set **Nozzle diameter** and **Layer height** to match your slicer
4. Set **Size (mm)** to your target print size
5. Optionally, open the **Stickers** panel to apply PNG or JPEG images onto the model surface
6. Adjust the palette and color settings — the output preview updates automatically
7. Use **File > Export 3MF** to save the result (defaults to `<input>.3mf`)
8. Open the exported 3MF in OrcaSlicer or BambuStudio and print
6. Optionally, open the **Split** panel to cut the model in two halves that print side-by-side and assemble with pegs
7. Adjust the palette and color settings — the output preview updates automatically
8. Use **File > Export 3MF** to save the result (defaults to `<input>.3mf`)
9. Open the exported 3MF in OrcaSlicer or BambuStudio and print

All sidebar sections are collapsible — click a section header to fold or expand it.

**File > Open Recent** lists both recently opened models and recently used JSON settings files.

Expand Down Expand Up @@ -191,6 +194,44 @@ regions that are nearly a single solid color.
Set the value with the **Color snap (delta E)** slider (0 to 50, default 5).
Set to 0 to disable.

## How to Split a Model into Two Halves

The **Split** panel cuts the model along an axis-aligned plane into two halves
that print separately and assemble back into the original. Both halves are
laid out side by side on the build plate, sitting flat on the cut face. Use
this when the model is taller than your build volume, when supports for an
overhang would otherwise be hard to remove, or when you want to paint each
half before assembly.

To split a model:

1. Open the **Split** panel and check **Split into two parts**. Alpha-wrap is
forced on automatically — a clean cut needs a watertight input mesh.
2. Choose the **Cut plane** (XY, XZ, or YZ) and the **Offset** along that
axis. The 3D viewer overlays a translucent quad showing the live cut
position.
3. Pick a **Connector style**:
- **Pegs** — a solid peg on one half mates with a matching pocket on the
other. Best for FDM where dowel hardware isn't on hand.
- **Dowel holes** — matching pockets on both halves; print or buy
separate dowel pins to glue in.
- **None** — flat cut, glue-only assembly.
4. Adjust **Count** (number of connectors along the cut; **Auto** picks 1, 2,
or 3 based on the cut polygon's inscribed-circle radius), **Diameter**,
**Depth**, **Clearance** (per-side radial gap on the female feature so
the peg slides in), and **Bed gap** (space between the two halves on the
plate) as needed.
5. Export the result with **File > Export 3MF** as usual. The exported file
contains two build items, one per half, that the slicer treats as
independent objects.

Stickers, color pins, and base color are applied to the original (unsplit)
mesh, so they survive the cut and appear on whichever half they land on.
Split panel state is saved and restored with the JSON settings file.

If you turn off **Alpha-wrap** while Split is enabled, Split is automatically
disabled as well. A toast explains the dependency.

## How to Save and Load Settings

Use **File > Save JSON** to save all current settings — palette, color pins,
Expand Down Expand Up @@ -227,29 +268,45 @@ compatible with OrcaSlicer and BambuStudio.
frontmost surface; "Unfold" mode flood-fills from the placement point
across mesh adjacency. Sticker colors are alpha-composited over the base
texture.
4. **Voxelize** — maps the model onto a grid of cells matching the nozzle and
4. **Split** (optional) — cuts the geometry mesh along the configured plane
using CGAL's `Polygon_mesh_processing::clip`, bakes peg or dowel
connectors into the cut faces via boolean ops, and lays the two halves
side by side on the build plate. Color sampling stays in the original
mesh's coordinate frame, so stickers, color pins, and base color
survive the cut unchanged.
5. **Voxelize** — maps the model onto a grid of cells matching the nozzle and
layer settings. Each cell gets the color sampled from the original texture
(including any stickers). First-layer cells are wider (`nozzle × 1.275`);
upper cells are narrower (`nozzle × 1.05`).
5. **Color adjust** — applies brightness, contrast, and saturation.
6. **Color warp** — applies color pin remappings using Gaussian RBF
6. **Color adjust** — applies brightness, contrast, and saturation.
7. **Color warp** — applies color pin remappings using Gaussian RBF
interpolation in CIELAB color space.
7. **Palette** — resolves locked colors, then selects auto colors from the
8. **Palette** — resolves locked colors, then selects auto colors from the
active collection. Applies color snap to shift cell colors toward the palette.
8. **Dither** — assigns a palette color to each cell to approximate the original
9. **Dither** — assigns a palette color to each cell to approximate the original
texture. The default `dizzy` mode uses random traversal with error diffusion
to spatial neighbors, producing blue-noise-like patterns. `none` assigns the
nearest palette color with no dithering.
9. **Clip** — cuts the decimated mesh along voxel color boundaries and assigns
each fragment a palette color.
10. **Merge** — merges coplanar triangles to reduce face count.
11. **Export** — writes a 3MF file with per-face material assignments.
10. **Clip** — cuts the decimated mesh along voxel color boundaries and assigns
each fragment a palette color.
11. **Merge** — merges coplanar triangles to reduce face count.
12. **Export** — writes a 3MF file with per-face material assignments. When
Split is enabled, two `<object>` entries are emitted (one per half) so
slicers see them as independent build items.

If **Alpha-wrap** is enabled (Advanced section), it runs between Load and
Decimate to produce a watertight shell of the input mesh.
Decimate to produce a watertight shell of the input mesh. Split also forces
alpha-wrap on, since the cut needs a watertight input.

Each stage is cached by its settings hash. Changing a downstream parameter
(e.g., dithering mode) skips all upstream stages on the next run.
(e.g., dithering mode) skips all upstream stages on the next run. The Load,
Decimate, and Alpha-wrap stage caches persist across app restarts on disk
(zstd-compressed), so re-opening a recent model is much faster than the
first time.

While the pipeline runs, the output stage list shows live progress for each
stage along with cache hit/miss status. If a stage fails, the error message
appears as a final line in the list.

---

Expand All @@ -265,8 +322,9 @@ ditherforge-cli model.glb --size 100
This loads `model.glb`, scales it to 100 mm, selects 4 colors from the default
palette, and writes `model.3mf` alongside the input.

Note: the CLI does not currently support stickers, color pins, or multi-object
selection. Use the GUI to configure those and save a JSON settings file.
Note: the CLI does not currently support stickers, color pins, splitting, or
multi-object selection. Use the GUI to configure those and save a JSON
settings file.

### Options

Expand Down Expand Up @@ -401,6 +459,27 @@ contrast, and saturation adjustments as the rest of the model.

Stickers are saved as part of the JSON settings file.

### Split

Cuts the model along an axis-aligned plane into two halves laid out side by
side on the build plate. Optional connectors register the halves during
glue-up.

| Field | Default | Description |
|-------|---------|-------------|
| Split into two parts | off | Master toggle. When off, the rest of the section is hidden and the pipeline behaves as if Split didn't exist. Forces Alpha-wrap on; turning Alpha-wrap off auto-disables Split. |
| Cut plane | XY | Axis-aligned plane: XY (cut along Z), XZ (cut along Y), or YZ (cut along X). |
| Offset (mm) | bbox mid | Position of the cut plane along the chosen axis, measured from the model's local origin. Adjustable via number field or slider. |
| Connector style | Pegs | `Pegs` (built-in male/female), `Dowel holes` (matching pockets, separate dowel pins), or `None` (flat cut). |
| Count | Auto | Number of connectors. `Auto` picks 1, 2, or 3 based on the cut polygon's inscribed-circle radius. |
| Diameter (mm) | 5.0 | Connector diameter. Hidden when style is None. |
| Depth (mm) | 6.0 | Connector depth (per side for dowels). Hidden when style is None. |
| Clearance (mm) | 0.15 | Per-side radial clearance applied to the female feature so the peg slides in. |
| Bed gap (mm) | 5.0 | Space between the two halves on the build plate. |

While the Split panel is open, a translucent overlay in the 3D viewer shows
the live cut plane through the input model.

### Color Pins (Warp Pins)

Each pin maps a source color to a target filament color using Gaussian RBF
Expand Down Expand Up @@ -452,7 +531,7 @@ solid-color regions.

Saved settings include: input file path, size/scale, nozzle diameter, layer
height, palette (locked colors and collection), color adjustments, color pins,
stickers, dither mode, color snap, and advanced flags.
stickers, dither mode, color snap, split configuration, and advanced flags.

### Advanced Options (GUI)

Expand Down
Loading
Loading