vaseweld
Spiral vase mode is all-or-nothing in every slicer. vaseweld welds two G-code files sliced from the same plate at a Z height you pick, so the vase can start above a solid base, or stop below a solid lid.
Drawn bead by bead from the welded output below, not a photo and not a mock-up. Blue is the normal
slice, orange is the spiral vase section, and the two seams are at Z=6.2 and Z=30.2. The model is
examples/vase_40mm.stl.
The output always uses relative extrusion (M83). If your slice uses absolute E (M82),
vaseweld converts it. This is not optional: the flow ramp across the transition layer scales
extrusion, and a scaled number is only meaningful as a delta. OrcaSlicer's own SpiralVase.cpp
carries the same note, that "Tapering of the transition layer only works reliably with relative
extruder distances". Any post-processing script you run after vaseweld has to expect relative E.
The problem
Slicers put spiral vase behind a single checkbox that applies to the whole print. People have been asking for a height range for six years:
- PrusaSlicer #3204, open since November 2019, 88 reactions, 42 comments. Latest activity is the bot warning it may be auto-closed as a legacy issue.
- PrusaSlicer #9340, height range modifiers are disabled in vase mode.
- Cura #7893, open since 2020, 27 comments.
- OrcaSlicer #4625, closed as not planned.
- BambuStudio #9657, open, someone pointing out Simplify3D has done this for years.
The workaround people use is to slice twice and paste the files together in a text editor. That gets the layer boundary wrong, leaves the extruder in the wrong mode, strands a second start-up sequence in the middle of the file, and skips the flow transition the slicer would have done. vaseweld does the same job correctly.
Install
Pick one. They all produce byte-identical output.
pipx run vaseweld weld --help # no install
uvx vaseweld weld --help # no install
python -m pip install vaseweld # console script named vaseweld
Or download a single file that needs nothing but Python 3.10+:
curl -LO https://raw.githubusercontent.com/Booyaka101/vaseweld/main/vaseweld.py
python vaseweld.py weld --help
Or grab vaseweld.exe from the releases page if
you would rather not have Python at all.
Usage
Slice the same plate twice in PrusaSlicer, OrcaSlicer or BambuStudio. Once with Spiral Vase off, once with it on. Do not move, rescale or reorient the object in between. Save both files, then:
$ vaseweld weld --normal base.gcode --vase body.gcode --at 6.2 -o hybrid.gcode
note: removed the printing time estimate, which cannot be recomputed from these two files
cut snapped to Z=6.200 (layer 31)
normal: layers 1-30 (Z 0.200-6.000)
vase: layers 31-200 (Z 6.200-40.000)
E mode: absolute -> relative (converted)
transition ramp: 0.80 -> 1.00 over layer 31
wrote hybrid.gcode (31343 lines)
$ vaseweld check hybrid.gcode
OK: Z monotonic, E coherent (relative), retractions balanced, 1 temperature timeline
The note goes to stderr; everything else goes to stdout. That run is reproducible from this
repository. base.gcode and body.gcode are
examples/vase_normal_40mm.gcode and examples/vase_spiral_40mm.gcode, two PrusaSlicer 2.9.6
slices of examples/vase_40mm.stl at 0.2 mm layers.
--at is a height in millimetres, not a layer number. It snaps down to the nearest real layer and
tells you which one:
$ vaseweld weld --normal base.gcode --vase body.gcode --at 6.3 -o hybrid.gcode
requested Z=6.300 is between layers, snapping down
cut snapped to Z=6.200 (layer 31)
...
Repeat --at to alternate again. Two cuts give the shape people actually ask for, a solid base, a
vase body and a solid lid:
$ vaseweld weld --normal base.gcode --vase body.gcode --at 6.2 --at 30.2 -o hybrid.gcode
cuts snapped to Z=6.200 (layer 31), Z=30.200 (layer 151)
normal: layers 1-30 (Z 0.200-6.000)
vase: layers 31-150 (Z 6.200-30.000)
normal: layers 151-200 (Z 30.200-40.000)
E mode: absolute -> relative (converted)
transition ramp: 0.80 -> 1.00 over layer 31
transition ramp: 1.00 -> 0.25 over layer 150
wrote hybrid.gcode (45013 lines)
Every seam gets its own travel, retraction match and flow ramp, so the spiral ramps up where it starts and back down where it ends.
If you would rather not guess at the cut height, ask:
$ vaseweld layers body.gcode
vase_spiral_40mm.gcode: 200 layers, Z 0.200 to 40.000
layer height: 0.200 mm
weldable range: Z 0.400 to 40.000 (layers 2 to 200)
--vase-first inverts the order, for a vase body with a solid lid on top:
$ vaseweld weld --normal base.gcode --vase body.gcode --at 30.2 --vase-first -o hybrid.gcode
cut snapped to Z=30.200 (layer 151)
vase: layers 1-150 (Z 0.200-30.000)
normal: layers 151-200 (Z 30.200-40.000)
E mode: absolute -> relative (converted)
transition ramp: 1.00 -> 0.25 over layer 150
wrote hybrid.gcode (35289 lines)
OrcaSlicer files already use relative E, so nothing is converted, and the flow ratios come from the file's own config block:
$ vaseweld weld --normal orca_base.gcode --vase orca_body.gcode --at 12.4 -o hybrid.gcode
cut snapped to Z=12.400 (layer 62)
normal: layers 1-61 (Z 0.200-12.200)
vase: layers 62-200 (Z 12.400-40.000)
E mode: relative (unchanged)
transition ramp: 0.00 -> 1.00 over layer 62
wrote hybrid.gcode (25647 lines)
0.00 there is OrcaSlicer's shipped default for spiral_starting_flow_ratio, which is what Orca
itself would ramp from. If that under-extrudes at the seam, pass --start-flow 0.8.
Running it from the slicer
You can let the slicer call vaseweld on the file it just wrote. Put this in Print settings > Output options > Post-processing scripts on the profile that has Spiral Vase turned on:
"C:\Program Files\vaseweld\vaseweld.exe" weld --normal "C:\prints\base.gcode" --at 12.4
The slicer appends the absolute path of a temporary G-code file as the last argument. vaseweld takes
that file as whichever side you left out, here the vase side, and rewrites it in place. It also
reads SLIC3R_PP_OUTPUT_NAME, the name the slicer will save under, and prints it so you can see
where the file is going.
Two things to watch. Use absolute paths and quote them, because the field is not a shell. And turn
off Supports binary G-code in the same Output options panel. vaseweld reads .bgcode fine, but
it writes plain G-code, so it will not overwrite a .bgcode file the slicer is about to save under
that name. Printers that accept .bgcode accept plain G-code too.
What it actually does
- Splits both files into header, layers keyed by Z, and footer, using the
;LAYER_CHANGEand;Z:markers that PrusaSlicer, OrcaSlicer and BambuStudio all emit, and falling back to bare Z moves. - Refuses the weld unless both files agree on
layer_height,first_layer_height,nozzle_diameter,filament_diameter,bed_shape,printer_model, the object instance count, and where the object sits on the bed. A mismatch names the field. - Emits relative E throughout, converting absolute-E segments by differencing consecutive values,
and resets with
G92 E0at the seam. - Ramps the flow across the transition layer the way the slicer would, from
spiral_starting_flow_ratioup to 1.0 across the first vase layer, or from 1.0 down tospiral_finishing_flow_ratioacross the last one. Fallbacks are 0.8 and 0.25 when the key is absent, as it is in PrusaSlicer. - Travels to where the vase slice expects the nozzle, and matches the retraction state that slice assumes. A vase layer takes for granted that the nozzle is already on its spiral, because in the source file the previous layer ended there. Without the travel the first spiral move drags a line across the print. The retraction part matters because slicers disagree about who owns the layer-change retraction: PrusaSlicer and OrcaSlicer put it at the start of the next layer, BambuStudio at the end of the previous one, so welding them naively leaves the nozzle either double-retracted or double-primed.
- Rewrites Klipper's
SET_PRINT_STATS_INFO TOTAL_LAYERandCURRENT_LAYER, thetotal layers countcomments, and BambuStudio'sHEADER_BLOCKtotals. - Recomputes the filament totals exactly, and remaps
M73progress and the printing time estimate using the two files' own remaining-time values. When there is noM73data to remap from, it strips the time comments rather than leave them wrong. - Reads binary G-code (
.bgcode) as well as text, decoding it in the tool with no converter on PATH and no dependencies. Binarising moves the config block and the filament and time totals out of the G-code stream into metadata blocks, so they are put back where a text file would have had them and everything downstream works unchanged. The trailing comment on each command (G28 ; home all axes) is gone for good; the binariser drops it. - Passes
G2/G3arc moves through with their geometry untouched. Their E values are still converted, because leaving them absolute in a relative file would break the print. - Writes a
; vaseweldprovenance block into the header naming both inputs and the cut.
See it without a printer
Open the demo and drag the slider. Three welds of the same pair of slices, drawn from their real G-code, nothing to install.
vaseweld preview writes the same thing for your own file, one self-contained HTML file. Open it in any browser and drag the slider:
every bead is drawn at the width the G-code actually asks for, coloured by which slice it came from.
$ vaseweld preview hybrid.gcode
wrote hybrid.html (418 KB), open it in any browser
That is layer 31 of the two-cut weld, from above. The ghosted blue underneath is the last normal layer, walls and gyroid infill; the orange loop on top is the first spiral layer. The front view builds up as you scrub, which is the image at the top of this page.
To rebuild that demo from a fresh clone, with nothing installed:
python sim/demo.py
That welds the two committed example slices three ways, runs vaseweld check on each, and writes
docs/demo/index.html linking the three previews.
Proof it prints
The unit tests prove the output is well formed. sim/ proves a printer would accept it, by feeding
every case to Klipper's own host process in batch mode, which plans each move through the real
cartesian kinematics and the real extruder limits with no printer attached.
docker build -t klipper-batch sim/ && python sim/build_cases.py
docker run --rm -v "$PWD/sim:/work" klipper-batch bash /work/run.sh
PASS 1_absolute_source_normal print time 368.737s
PASS 1_absolute_source_vase print time 305.882s
FAIL 2_absolute_naive_text_editor klippy exit 255, 40 rejected moves
Move exceeds maximum extrusion (10.116mm^2 vs 0.640mm^2)
PASS 2_relative_naive_text_editor print time 304.025s
PASS 3_absolute_vaseweld print time 299.385s
PASS 3_relative_vaseweld print time 299.512s
PASS 4_absolute_vaseweld_vase_first print time 371.931s
2_naive_text_editor is the workaround people use today: paste the two files together at a layer
boundary. With absolute E, which is PrusaSlicer's default, the printer refuses it, because every E
value after the paste is read as an absolute position. With relative E it plans fine but still prints
a 12.5 mm scar, because the nozzle is left where the other file stopped and the spiral starts
somewhere else:
| first move after the cut | travel | filament | flow vs normal |
|---|---|---|---|
| hand splice, absolute E | 12.522 mm | 33.03150 mm | 7794% |
| hand splice, relative E | 12.522 mm | 0.01694 mm | 4% |
| vaseweld, either | 0.500 mm | 0.01359 mm | 80.2% |
That 80.2% is the transition ramp starting from --start-flow 0.8.
sim/deposit.py takes it the rest of the way: it turns every extruding move into a bead of known
width and draws the weld layer from above, so you can see what the two files put on the plate.
The red line is a real extrusion, 0.06 mm wide, laid straight across the open middle of the vase. That is what a hand splice does at the seam, because the nozzle is still where the other file left it. The same model sawn through the wall:
Bead widths at the weld layer, against a 0.450 mm nominal:
| hand splice | vaseweld | |
|---|---|---|
| the wall | 0.450 mm, no ramp | 0.425 mm, the ramp starting at 80% |
| across the part | 0.059 mm thread | nothing |
tests/test_weld.py carries this as a regression guard: no printing move at the weld layer may lay
a bead outside half to twice the layer's own nominal width, checked for all three slicers, with a
companion test that the same measurement does catch a hand splice. See sim/README.md.
The other commands
vaseweld layers FILE prints the Z ladder and the weldable range.
vaseweld preview FILE writes the HTML page above.
vaseweld check FILE works on any G-code file, welded or not. It runs four checks:
| check | what it catches |
|---|---|
| Z monotonic | an extruding move below a height already printed, the classic bad splice |
| E coherent | an absolute E value stranded in a relative file, or a mid-print M82/M83 switch |
| retractions balanced | more filament pulled back at once than one retraction's worth, which means an unretract went missing |
| temperature timeline | a blocking M109/M190 in the middle of the print, which is the start-up sequence of a second file |
Exit codes are 0 for OK, 1 for problems found, 2 for a file it could not read.
$ vaseweld check broken.gcode
FAIL: 1 problem in broken.gcode
line 12695: blocking temperature wait (M109 S215) in the middle of the print; this is the start-up sequence of a second file
Options
vaseweld weld takes:
--normal PATH the non-vase slice
--vase PATH the spiral vase slice
--at Z cut height in mm, snapped down to a real layer; repeat to alternate again
-o, --output PATH file to write
--vase-first start with the vase part below the first cut
--start-flow RATIO override spiral_starting_flow_ratio (0 to 1)
--finish-flow RATIO override spiral_finishing_flow_ratio (0 to 1)
--no-seam-retract do not retract before the seam travel
--dry-run report the plan and write nothing
--force weld despite a profile mismatch, and say what was ignored
and what that does to the seam
Limitations
These are out of scope for 1.0, not bugs:
- Binary
.bgcodecan be read but not written. The output is always text G-code. - Single object, single material. This is the same constraint PrusaSlicer's own validator enforces, and vaseweld quotes it back at you: "The Spiral Vase option can only be used when printing single material objects."
- vaseweld does not slice. You bring both files.
- The printing time estimate can only be recomputed when the files carry
M73remaining times, which PrusaSlicer emits only with "Supports remaining times" enabled. Otherwise the estimate is stripped. - Adaptive or variable layer height will make the two files disagree on layer Z. Slice both with a fixed layer height.
Development
git clone https://github.com/Booyaka101/vaseweld
cd vaseweld
python -m pytest
138 tests, about 40 seconds, no dependencies beyond pytest. Everything runs against real slicer
output committed under tests/fixtures/, produced by driving PrusaSlicer 2.9.6, OrcaSlicer 2.4.2
and BambuStudio 02.08.02.61 from the command line over the models in examples/. See
tests/fixtures/README.md for the exact commands.
vaseweld.py at the repository root is generated from src/vaseweld/ by
python tools/build_single_file.py. A test fails if it drifts.
License
MIT.
Release files for vaseweld 1.3.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| vaseweld-1.3.0.tar.gz | 61.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| vaseweld-1.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 103.9 kB
Release files / vaseweld-1.3.0.tar.gz
| Download URL | vaseweld-1.3.0.tar.gz |
|---|---|
| Size | 61.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7a638f6824180dedc539a713e29f3e47710c3571994cdf99f4f94d118d2a710a
|
|
BLAKE2b-256 checksum How to use checksums |
e56b84916c0d875dbbb6681ffd019a33c1ec8abe38267f4c5db54e6687191ab6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|
Release files / vaseweld-1.3.0-py3-none-any.whl
| Download URL | vaseweld-1.3.0-py3-none-any.whl |
|---|---|
| Size | 42.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9f14b53b075fe39edae02a0c42b91ca83b7b6015abd8cce654a3d7f6d1180ace
|
|
BLAKE2b-256 checksum How to use checksums |
6f6a133a9a199d8c73598408fa7301132893fb3cb1e2c38d6fc5a17442f82e1a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.9
|