Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@ This is a temporary workaround. We will remove this change after Bambu Lab fixes

**Acknowledgements**: Thanks to alexbreinig for pointing out this issue and capsel22 for reporting it in the BambuStudio issues. For more details, see: https://github.com/bambulab/BambuStudio/issues/8801

### Missing Presets for Some Nozzle / Extruder Options

**Issue**: Bambu Lab printers that offer more than one extruder/nozzle option (for example the multi-nozzle machines such as X2D and the H2 series) store a separate set of values for each option inside a single filament preset. The available options are:

- `Direct Drive Standard`
- `Direct Drive High Flow`
- `Bowden Standard`
- `Bowden High Flow`

For some materials we have only tuned and authored values for one option (typically `Direct Drive Standard`). The remaining options are left empty (`nil`) in the preset, which means the auxiliary / additional nozzle has **no tuned values** for that material. This includes the auxiliary (Bowden) nozzle on dual-nozzle machines.

**What we do about it**: When you download presets from the [download page](https://presets.polymaker.com), the site checks each selected preset. If any of them are missing values for one or more nozzle/extruder options, a warning appears before the download starts. The warning lists exactly which material and which options have no preset, for example:

> We didn't make presets for these variants:
> - PolyLite PLA — Bambu Lab X2D: Direct Drive High Flow, Bowden Standard, Bowden High Flow

You can still continue the download — the listed nozzle options simply won't have tuned values, so they fall back to the slicer's generic settings. Printing on the option that **does** have a preset (usually the main Direct Drive nozzle) is unaffected.

We are progressively adding presets for the remaining nozzle options. If a material you need is missing values for a nozzle option you use, please let us know.

## 🌐 Download Presets

**Visit our download page:** [https://presets.polymaker.com](https://presets.polymaker.com)
Expand Down
184 changes: 153 additions & 31 deletions app.js
Original file line number Diff line number Diff line change
Expand Up @@ -918,10 +918,38 @@ function init() {
}

// Download multiple presets as ZIP
function downloadSelectedPresets() {
// Aggregate the selected presets into a de-duplicated list of
// { material, model, variants } for those missing extruder variants.
function collectMissingVariantWarnings(presets) {
var seen = {};
var items = [];
for (var key in presets) {
if (!Object.prototype.hasOwnProperty.call(presets, key)) continue;
var p = presets[key];
var missing = p && p.missingExtruderVariants;
if (!missing || !missing.length) continue;
var dedupKey = (p.material || '') + '||' + (p.model || '') + '||' + missing.join(',');
if (seen[dedupKey]) continue;
seen[dedupKey] = true;
items.push({ material: p.material || '', model: p.model || '', variants: missing.slice() });
}
return items;
}

function downloadSelectedPresets(skipVariantWarning) {
var presetIds = Object.keys(selectedPresets);
if (presetIds.length === 0) return;

if (!skipVariantWarning) {
var variantWarnings = collectMissingVariantWarnings(selectedPresets);
if (variantWarnings.length) {
showMissingVariantWarning(variantWarnings, function () {
downloadSelectedPresets(true);
});
return;
}
}

// Show loading state
if (downloadSelectedBtn) {
downloadSelectedBtn.disabled = true;
Expand Down Expand Up @@ -1040,7 +1068,16 @@ function init() {
});
}

function downloadSelectedBundle() {
function downloadSelectedBundle(skipVariantWarning) {
if (!skipVariantWarning) {
var variantWarnings = collectMissingVariantWarnings(selectedPresets);
if (variantWarnings.length) {
showMissingVariantWarning(variantWarnings, function () {
downloadSelectedBundle(true);
});
return;
}
}
// Filter for BambuStudio presets only
var bambuPresets = [];
for (var key in selectedPresets) {
Expand Down Expand Up @@ -1392,12 +1429,34 @@ function init() {
showBambuRestartWarning(action, function () {});
}

// For a single-row download: read the row's preset payload and, if it has
// missing extruder variants, show the warning modal before proceeding.
function withMissingVariantWarning(linkEl, action) {
var row = linkEl ? linkEl.closest('tr') : null;
var label = row ? row.querySelector('.preset-checkbox') : null;
if (label) {
try {
var d = JSON.parse(label.getAttribute('data-preset-data'));
if (d && d.missingExtruderVariants && d.missingExtruderVariants.length) {
showMissingVariantWarning(
[{ material: d.material || '', model: d.model || '', variants: d.missingExtruderVariants.slice() }],
action
);
return;
}
} catch (e) { /* fall through to action */ }
}
action();
}

if (downloadSelectedBtn) {
downloadSelectedBtn.addEventListener('click', downloadSelectedPresets);
// Wrap so the click Event isn't passed as skipVariantWarning (which would
// bypass the missing-variant guard); the internal re-invoke passes true.
downloadSelectedBtn.addEventListener('click', function () { downloadSelectedPresets(); });
}

if (downloadBundleBtn) {
downloadBundleBtn.addEventListener('click', downloadSelectedBundle);
downloadBundleBtn.addEventListener('click', function () { downloadSelectedBundle(); });
}

// Handle strict checkbox change
Expand Down Expand Up @@ -1552,7 +1611,7 @@ function init() {
var filename = displayFilename(p.filename, p.slicer);
var presetId = p.path || (p.material + '-' + p.brand + '-' + p.model + '-' + p.slicer);
var isChecked = selectedPresets[presetId] ? ' checked' : '';
var presetData = JSON.stringify({ url: url, filename: filename, slicer: p.slicer, path: p.path, material: p.material, model: p.model, brand: p.brand });
var presetData = JSON.stringify({ url: url, filename: filename, slicer: p.slicer, path: p.path, material: p.material, model: p.model, brand: p.brand, missingExtruderVariants: p.missingExtruderVariants || [] });
var checkboxHtml = '<label class="checkbox-label preset-checkbox" data-preset-id="' + escapeHtml(presetId) + '" data-preset-data="' + escapeHtml(presetData) + '"><input type="checkbox" class="checkbox-input preset-checkbox-input"' + isChecked + '><span class="checkbox-custom"></span></label>';
var printerBrand = getPrinterBrand(p.compatiblePrinters, p.brand);
var compatiblePrintersList = formatCompatiblePrinters(p.compatiblePrinters);
Expand Down Expand Up @@ -1737,7 +1796,9 @@ function init() {
});
}

withBambuRestartWarning(doDownloadBambuJson);
withMissingVariantWarning(bambuJsonLink, function () {
withBambuRestartWarning(doDownloadBambuJson);
});
return;
}

Expand All @@ -1755,31 +1816,35 @@ function init() {
return;
}

// Fetch the preset JSON to get filament_vendor
fetch(url, { mode: 'cors' })
.then(function (r) {
if (!r.ok) throw new Error('Failed to fetch preset: ' + r.statusText);
return r.json();
})
.then(function (data) {
if (!data || !data.compatible_printers) {
console.warn('Preset data missing compatible_printers:', data);
}
var preset = {
path: url,
filename: filename,
material: data.name || material,
model: model,
slicer: 'BambuStudio',
filament_vendor: data.filament_vendor || ['Polymaker'],
presetData: data
};
downloadAsBbsflmt([preset]);
})
.catch(function (err) {
console.error('Error downloading bundle:', err);
alert(t('alert.error.preset', { msg: err.message }));
});
function doDownloadBundle() {
// Fetch the preset JSON to get filament_vendor
fetch(url, { mode: 'cors' })
.then(function (r) {
if (!r.ok) throw new Error('Failed to fetch preset: ' + r.statusText);
return r.json();
})
.then(function (data) {
if (!data || !data.compatible_printers) {
console.warn('Preset data missing compatible_printers:', data);
}
var preset = {
path: url,
filename: filename,
material: data.name || material,
model: model,
slicer: 'BambuStudio',
filament_vendor: data.filament_vendor || ['Polymaker'],
presetData: data
};
downloadAsBbsflmt([preset]);
})
.catch(function (err) {
console.error('Error downloading bundle:', err);
alert(t('alert.error.preset', { msg: err.message }));
});
}

withMissingVariantWarning(bundleLink, doDownloadBundle);
return;
}
});
Expand Down Expand Up @@ -1968,6 +2033,62 @@ function showBambuRestartWarning(onConfirm, onCancel) {
document.body.style.overflow = 'hidden';
}

var missingVariantModalState = {
onAck: null
};

function showMissingVariantWarning(items, onAck) {
var modal = document.getElementById('missing-variant-modal');
if (!modal || !items || !items.length) {
if (onAck) onAck();
return;
}

var listEl = document.getElementById('missing-variant-list');
if (listEl) {
var html = '';
for (var i = 0; i < items.length; i++) {
html += '<li><strong>' + escapeHtml(items[i].material) + ' — ' + escapeHtml(items[i].model) +
'</strong>: ' + escapeHtml(items[i].variants.join(', ')) + '</li>';
}
listEl.innerHTML = html;
}

missingVariantModalState.onAck = onAck;
modal.setAttribute('aria-hidden', 'false');
modal.classList.add('is-open');
document.body.style.overflow = 'hidden';
}

function initMissingVariantModal() {
var modal = document.getElementById('missing-variant-modal');
if (!modal) return;

var closeBtn = document.getElementById('missing-variant-modal-close');
var ackBtn = document.getElementById('missing-variant-ack');
var overlay = modal.querySelector('.modal-overlay');

// Acknowledge-only: every dismissal path proceeds with the download.
function ack() {
modal.setAttribute('aria-hidden', 'true');
modal.classList.remove('is-open');
document.body.style.overflow = '';
var cb = missingVariantModalState.onAck;
missingVariantModalState.onAck = null;
if (cb) cb();
}

if (closeBtn) closeBtn.addEventListener('click', ack);
if (ackBtn) ackBtn.addEventListener('click', ack);
if (overlay) overlay.addEventListener('click', ack);

document.addEventListener('keydown', function (e) {
if (e.key === 'Escape' && modal.classList.contains('is-open')) {
ack();
}
});
}

/**
* Format printer model name for display
* @param {string} model - Raw model name (e.g., "A1M", "A1")
Expand Down Expand Up @@ -2243,4 +2364,5 @@ init();
initModal();
initDuplicateModal();
initBambuRestartModal();
initMissingVariantModal();
initAccordion();
Loading
Loading