mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-22 03:49:18 +02:00
Merge branch 'dev' into feature/russian-localization
This commit is contained in:
@@ -259,6 +259,79 @@ describe('SliceModal', () => {
|
||||
await waitFor(() => expect(onClose).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('offers "use the file\'s built-in settings" when the printer matches the design, and sends the flag (#2611)', async () => {
|
||||
const onClose = vi.fn();
|
||||
mockApi.sliceLibraryFile.mockResolvedValue({
|
||||
job_id: 42,
|
||||
status: 'pending',
|
||||
status_url: '/api/v1/slice-jobs/42',
|
||||
});
|
||||
// A project 3MF whose embedded printer matches a listed preset — the
|
||||
// printer pre-pick lands on it, so selectedPrinterName === embedded and
|
||||
// the "slice as designed" toggle is offered.
|
||||
mockApi.getLibraryFilePlates.mockResolvedValue({
|
||||
file_id: 100,
|
||||
filename: 'Designed.3mf',
|
||||
plates: [],
|
||||
is_multi_plate: false,
|
||||
embedded_printer: 'Bambu Lab X1 Carbon 0.4 nozzle',
|
||||
embedded_process: '0.20mm Standard',
|
||||
});
|
||||
|
||||
renderWithTracker({
|
||||
source: { kind: 'libraryFile', id: 100, filename: 'Designed.3mf' },
|
||||
onClose,
|
||||
});
|
||||
|
||||
const user = userEvent.setup();
|
||||
const toggle = (await screen.findByLabelText(
|
||||
/Use the file's built-in settings/,
|
||||
)) as HTMLInputElement;
|
||||
expect(toggle.checked).toBe(false);
|
||||
|
||||
// All preset dropdowns are live until the toggle is on, then bypassed —
|
||||
// the printer included, so changing it can't silently drop the mode.
|
||||
const printerSelect = presetSelects()[0];
|
||||
const processSelect = presetSelects()[1];
|
||||
expect(printerSelect.disabled).toBe(false);
|
||||
expect(processSelect.disabled).toBe(false);
|
||||
await user.click(toggle);
|
||||
expect(printerSelect.disabled).toBe(true);
|
||||
expect(processSelect.disabled).toBe(true);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: /^Slice$/ }));
|
||||
await waitFor(() => {
|
||||
expect(mockApi.sliceLibraryFile).toHaveBeenCalledWith(
|
||||
100,
|
||||
expect.objectContaining({ use_embedded_settings: true }),
|
||||
);
|
||||
});
|
||||
await waitFor(() => expect(onClose).toHaveBeenCalled());
|
||||
});
|
||||
|
||||
it('hides the embedded-settings toggle when the picked printer differs from the design (#2611)', async () => {
|
||||
// Embedded target is a model with no matching preset in the listing, so
|
||||
// the printer pre-pick falls back to the local default (Imported X1C),
|
||||
// which does not match — honouring embedded settings would risk the
|
||||
// wrong bed, so the toggle stays hidden.
|
||||
mockApi.getLibraryFilePlates.mockResolvedValue({
|
||||
file_id: 100,
|
||||
filename: 'Designed.3mf',
|
||||
plates: [],
|
||||
is_multi_plate: false,
|
||||
embedded_printer: 'Bambu Lab P1S 0.4 nozzle',
|
||||
embedded_process: '0.20mm Standard',
|
||||
});
|
||||
|
||||
renderWithTracker({
|
||||
source: { kind: 'libraryFile', id: 100, filename: 'Designed.3mf' },
|
||||
onClose: vi.fn(),
|
||||
});
|
||||
|
||||
await waitFor(() => expect(screen.getByText('Imported X1C 0.4')).toBeDefined());
|
||||
expect(screen.queryByLabelText(/Use the file's built-in settings/)).toBeNull();
|
||||
});
|
||||
|
||||
it('includes bed_type in the request when the user picks a non-auto plate (#1337)', async () => {
|
||||
const onClose = vi.fn();
|
||||
mockApi.sliceLibraryFile.mockResolvedValue({
|
||||
|
||||
@@ -104,14 +104,15 @@ describe('SpoolBuddyLayout', () => {
|
||||
|
||||
it('suppresses the global toast viewport while mounted', () => {
|
||||
const { unmount } = renderLayout();
|
||||
// Visible viewport gets `hidden` class while the kiosk is up.
|
||||
const viewport = document.querySelector('div.fixed.bottom-4.right-20');
|
||||
// Visible viewport gets `hidden` class while the kiosk is up. Position is
|
||||
// set via safe-area calc() (#2612) so match the stable data-testid.
|
||||
const viewport = document.querySelector('[data-testid="toast-viewport"]');
|
||||
expect(viewport?.className).toContain('hidden');
|
||||
|
||||
// Cleanup restores the viewport when the kiosk unmounts (e.g. user
|
||||
// navigates back to the main app).
|
||||
unmount();
|
||||
const viewportAfter = document.querySelector('div.fixed.bottom-4.right-20');
|
||||
const viewportAfter = document.querySelector('[data-testid="toast-viewport"]');
|
||||
// After unmount the toast container is gone with the provider; the
|
||||
// important guarantee is the suppression flag was untoggled, which the
|
||||
// ToastContext tests pin directly. Here we only assert no crash on
|
||||
|
||||
@@ -118,8 +118,9 @@ describe('ToastContext viewport suppression', () => {
|
||||
</ToastProvider>
|
||||
);
|
||||
|
||||
// Toast viewport is the fixed-position container with bottom-4 right-20.
|
||||
const findViewport = () => container.querySelector('div.fixed.bottom-4.right-20');
|
||||
// Toast viewport is the fixed-position container; position is set via
|
||||
// safe-area calc() (#2612) so match the stable data-testid, not classes.
|
||||
const findViewport = () => container.querySelector('[data-testid="toast-viewport"]');
|
||||
expect(findViewport()?.className).not.toContain('hidden');
|
||||
|
||||
act(() => {
|
||||
@@ -140,4 +141,25 @@ describe('ToastContext viewport suppression', () => {
|
||||
});
|
||||
expect(findViewport()?.className).not.toContain('hidden');
|
||||
});
|
||||
|
||||
it('caps every toast to the viewport width so it cannot run off-screen (#2612)', () => {
|
||||
const { container, getByTestId } = render(
|
||||
<ToastProvider>
|
||||
<ViewportProbe />
|
||||
</ToastProvider>
|
||||
);
|
||||
|
||||
act(() => {
|
||||
getByTestId('show-toast').click();
|
||||
});
|
||||
|
||||
// The fixed-width dispatch toast (420px) overflowed the left edge of a
|
||||
// phone in an installed PWA. Every toast now carries a viewport-relative
|
||||
// max-width so it stays on-screen; pin it here.
|
||||
const viewport = container.querySelector('[data-testid="toast-viewport"]');
|
||||
const toast = viewport?.querySelector<HTMLElement>('div[style]');
|
||||
expect(toast?.style.maxWidth).toContain('100vw');
|
||||
expect(toast?.style.maxWidth).toContain('safe-area-inset-left');
|
||||
expect(toast?.style.maxWidth).toContain('safe-area-inset-right');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1543,6 +1543,11 @@ export interface SliceRequest {
|
||||
// "Textured PEI Plate", "Smooth PEI Plate", "Cool Plate (SuperTack)",
|
||||
// "Supertack Plate".
|
||||
bed_type?: string | null;
|
||||
// "Slice as designed" (#2611). 3MF only: slice using the file's embedded
|
||||
// project_settings.config (the designer's own wall count, infill, etc.)
|
||||
// instead of the picked profile triplet. The preset refs above are still
|
||||
// required by the backend validator but go unused on this path.
|
||||
use_embedded_settings?: boolean;
|
||||
}
|
||||
|
||||
// GET /api/v1/slicer/presets — unified listing across cloud / local / standard.
|
||||
|
||||
@@ -220,6 +220,13 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
|
||||
// user had no way to switch plates without cloning the preset.
|
||||
const [bedType, setBedType] = useState<string | null>(null);
|
||||
|
||||
// "Slice as designed" (#2611). When on, the backend honours the source
|
||||
// 3MF's embedded project_settings.config (the designer's own wall count,
|
||||
// infill, etc.) instead of the picked process/filament profiles. Only
|
||||
// offered when the picked printer matches the design's target model —
|
||||
// see canUseEmbedded below.
|
||||
const [useEmbedded, setUseEmbedded] = useState(false);
|
||||
|
||||
// Slicer Pipelines (#1425) — apply a saved preset bundle to all four slots
|
||||
// with one pick, or save the current selection as a new pipeline.
|
||||
const pipelinesQuery = useQuery({
|
||||
@@ -380,6 +387,24 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
|
||||
const embeddedPrinter = platesQuery.data?.embedded_printer ?? null;
|
||||
const embeddedProcess = platesQuery.data?.embedded_process ?? null;
|
||||
|
||||
// "Slice as designed" is offered only when the source carries embedded
|
||||
// settings (a real project 3MF, not an STL) AND the picked printer matches
|
||||
// the design's target model. The match gate is load-bearing: honouring
|
||||
// embedded settings for a different model would place the model on the
|
||||
// wrong bed. Names come from the same preset namespace, so a normalised
|
||||
// (strip "# " prefix, case-fold) equality is enough.
|
||||
const canUseEmbedded = useMemo<boolean>(() => {
|
||||
if (!embeddedPrinter || !embeddedProcess || !selectedPrinterName) return false;
|
||||
const norm = (s: string) => s.replace(/^#\s*/, '').trim().toLowerCase();
|
||||
return norm(selectedPrinterName) === norm(embeddedPrinter);
|
||||
}, [embeddedPrinter, embeddedProcess, selectedPrinterName]);
|
||||
|
||||
// Drop back to profile slicing whenever the toggle stops being offered
|
||||
// (e.g. the user switches to a printer that doesn't match the design).
|
||||
useEffect(() => {
|
||||
if (!canUseEmbedded) setUseEmbedded(false);
|
||||
}, [canUseEmbedded]);
|
||||
|
||||
// Printer pre-pick: defaults to the printer the 3MF was prepared for when
|
||||
// that preset is available, else the first listed printer. Runs once when
|
||||
// presets first arrive; later re-renders preserve any manual choice.
|
||||
@@ -476,6 +501,10 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
|
||||
filament_presets: filamentPresets as PresetRef[],
|
||||
...(plate != null ? { plate } : {}),
|
||||
...(bedType != null ? { bed_type: bedType } : {}),
|
||||
// The preset refs above are still sent (the backend validator requires
|
||||
// them) but go unused when this flag is set — the slicer falls back on
|
||||
// the file's embedded project_settings.config instead.
|
||||
...(useEmbedded && canUseEmbedded ? { use_embedded_settings: true } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -710,25 +739,53 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
|
||||
data={presetsQuery.data}
|
||||
value={printerPreset}
|
||||
onChange={setPrinterPreset}
|
||||
disabled={isEnqueuing}
|
||||
// Locked in embedded mode too: the picked printer is unused on
|
||||
// the embedded-settings path, and changing it away from the
|
||||
// design's target would drop canUseEmbedded and yank the toggle
|
||||
// out from under the user (#2611).
|
||||
disabled={isEnqueuing || useEmbedded}
|
||||
/>
|
||||
{/* "Slice as designed" (#2611): honour the file's embedded
|
||||
settings instead of the picked process/filament. Offered
|
||||
only when the picked printer matches the design's target. */}
|
||||
{canUseEmbedded && (
|
||||
<label className="flex items-start gap-2 text-sm text-bambu-gray cursor-pointer select-none">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={useEmbedded}
|
||||
onChange={(e) => setUseEmbedded(e.target.checked)}
|
||||
disabled={isEnqueuing}
|
||||
className="mt-0.5 cursor-pointer"
|
||||
/>
|
||||
<span>
|
||||
{t('slice.useEmbedded')}
|
||||
<span className="block text-xs text-bambu-gray/70">
|
||||
{t('slice.useEmbeddedHint')}
|
||||
</span>
|
||||
</span>
|
||||
</label>
|
||||
)}
|
||||
<PresetDropdown
|
||||
label={t('slice.process')}
|
||||
slot="process"
|
||||
data={presetsQuery.data}
|
||||
value={processPreset}
|
||||
onChange={setProcessPreset}
|
||||
disabled={isEnqueuing}
|
||||
disabled={isEnqueuing || useEmbedded}
|
||||
selectedPrinterName={selectedPrinterName}
|
||||
compatIndex={compatIndex}
|
||||
/>
|
||||
{/* Bed-type override (#1337). Always visible, always enabled.
|
||||
The backend patches curr_bed_type on the resolved process
|
||||
JSON before forwarding to the sidecar. */}
|
||||
{/* Bed-type patches curr_bed_type onto the resolved process
|
||||
JSON, which the embedded-settings path never sends — so it
|
||||
has no effect there and is disabled to avoid implying it
|
||||
does. */}
|
||||
<BedTypeDropdown
|
||||
value={bedType}
|
||||
onChange={setBedType}
|
||||
disabled={isEnqueuing}
|
||||
disabled={isEnqueuing || useEmbedded}
|
||||
/>
|
||||
{/* Filament reqs may need a server-side preview-slice for
|
||||
unsliced project files (single-pass, then cached). Show a
|
||||
@@ -775,7 +832,7 @@ export function SliceModal({ source, onClose }: SliceModalProps) {
|
||||
return next;
|
||||
})
|
||||
}
|
||||
disabled={isEnqueuing || !isUsed}
|
||||
disabled={isEnqueuing || !isUsed || useEmbedded}
|
||||
swatchColor={filamentSlots.length > 1 ? slot.color : undefined}
|
||||
selectedPrinterName={selectedPrinterName}
|
||||
compatIndex={compatIndex}
|
||||
|
||||
@@ -316,14 +316,32 @@ export function ToastProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
{/* Toast Container — to the left of the bug-report bubble (bottom-4 right-4 w-12).
|
||||
The kiosk layout suppresses this entire viewport so SpoolBuddy displays stay
|
||||
free of main-app notifications. */}
|
||||
<div className={`fixed bottom-4 right-20 z-[60] flex flex-col items-end gap-2 ${viewportSuppressed ? 'hidden' : ''}`}>
|
||||
free of main-app notifications.
|
||||
Position is set via safe-area-aware calc() rather than bottom-4/right-20 so an
|
||||
installed PWA on a notched phone clears the home indicator / landscape notch
|
||||
(#2612): the 5rem right offset keeps clearance for the bug bubble. */}
|
||||
<div
|
||||
data-testid="toast-viewport"
|
||||
className={`fixed z-[60] flex flex-col items-end gap-2 ${viewportSuppressed ? 'hidden' : ''}`}
|
||||
style={{
|
||||
bottom: 'calc(1rem + env(safe-area-inset-bottom))',
|
||||
right: 'calc(5rem + env(safe-area-inset-right))',
|
||||
}}
|
||||
>
|
||||
{toasts.map((toast) => (
|
||||
<div
|
||||
key={toast.id}
|
||||
className={`rounded-lg border shadow-lg backdrop-blur-sm animate-slide-in ${bgColors[toast.type]} ${
|
||||
toast.dispatchData ? 'w-[420px] p-3' : 'flex items-center gap-3 px-4 py-3'
|
||||
}`}
|
||||
// Cap width to the viewport so the fixed-width dispatch toast (420px)
|
||||
// can't run off the left edge on a phone (#2612). At the cap the toast
|
||||
// sits 1rem + safe-area from the left; on desktop the 420px wins. The
|
||||
// 6rem = the 5rem right offset above + a 1rem left gutter.
|
||||
style={{
|
||||
maxWidth:
|
||||
'calc(100vw - 6rem - env(safe-area-inset-left) - env(safe-area-inset-right))',
|
||||
}}
|
||||
data-testid={toast.dispatchData ? 'dispatch-toast-wrapper' : undefined}
|
||||
>
|
||||
{toast.dispatchData ? (
|
||||
@@ -389,11 +407,14 @@ export function ToastProvider({ children }: { children: ReactNode }) {
|
||||
data-testid={`dispatch-toast-job-${job.jobId}`}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<span className="text-xs text-white truncate" title={job.sourceName}>
|
||||
{/* min-w-0 + flex-1 lets truncate actually kick in
|
||||
when the toast is capped to a phone's width
|
||||
(#2612); the status chip stays put with shrink-0. */}
|
||||
<span className="text-xs text-white truncate min-w-0 flex-1" title={job.sourceName}>
|
||||
{job.sourceName}
|
||||
</span>
|
||||
<span
|
||||
className="text-[11px] uppercase tracking-wide text-bambu-gray"
|
||||
className="text-[11px] uppercase tracking-wide text-bambu-gray shrink-0"
|
||||
data-testid={`dispatch-toast-status-${job.jobId}`}
|
||||
>
|
||||
{t(`dispatchToast.status.${job.status}`)}
|
||||
|
||||
@@ -4032,6 +4032,8 @@ export default {
|
||||
refreshPresets: 'Aktualisieren',
|
||||
refreshPresetsTitle: 'Profile neu laden — die aktuellen Cloud- und Bundle-Listen abrufen (nach dem Löschen eines Profils in Bambu Studio oder Bambu Handy verwenden)',
|
||||
allPresetsRequired: 'Alle Profile müssen ausgewählt sein',
|
||||
useEmbedded: 'Eingebettete Einstellungen der Datei verwenden',
|
||||
useEmbeddedHint: 'So slicen, wie der Ersteller es angelegt hat (Wände, Füllung, Filament), statt mit den obigen Profilen. Verfügbar, weil dein Drucker zur Datei passt.',
|
||||
enqueuing: 'Slice-Auftrag wird übermittelt…',
|
||||
queued: 'In Warteschlange…',
|
||||
failed: 'Slicen fehlgeschlagen. Logs des Slicer-Sidecars prüfen.',
|
||||
|
||||
@@ -4066,6 +4066,8 @@ export default {
|
||||
refreshPresets: 'Refresh',
|
||||
refreshPresetsTitle: 'Refresh presets — fetch the latest cloud and bundled listings (use after deleting a preset in Bambu Studio or Bambu Handy)',
|
||||
allPresetsRequired: 'All presets must be selected',
|
||||
useEmbedded: "Use the file's built-in settings",
|
||||
useEmbeddedHint: "Slice it the way the designer set it up (walls, infill, filament) instead of the profiles above. Offered because your printer matches the file's.",
|
||||
enqueuing: 'Submitting slice job…',
|
||||
queued: 'Queued…',
|
||||
failed: 'Slicing failed. Check the slicer sidecar logs.',
|
||||
|
||||
@@ -4035,6 +4035,8 @@ export default {
|
||||
refreshPresets: 'Actualizar',
|
||||
refreshPresetsTitle: 'Actualizar preajustes — recuperar los listados más recientes de la nube y los paquetes (úselo tras eliminar un preajuste en Bambu Studio o Bambu Handy)',
|
||||
allPresetsRequired: 'Deben seleccionarse todos los preajustes',
|
||||
useEmbedded: 'Usar la configuración incorporada del archivo',
|
||||
useEmbeddedHint: 'Laminar tal como lo configuró el diseñador (perímetros, relleno, filamento) en lugar de los perfiles de arriba. Disponible porque tu impresora coincide con la del archivo.',
|
||||
enqueuing: 'Enviando el trabajo de laminado…',
|
||||
queued: 'En cola…',
|
||||
failed: 'Error al laminar. Consulte los registros del contenedor auxiliar del laminador.',
|
||||
|
||||
@@ -4021,6 +4021,8 @@ export default {
|
||||
refreshPresets: 'Actualiser',
|
||||
refreshPresetsTitle: 'Actualiser les préréglages — récupérer les dernières listes Cloud et bundle (à utiliser après avoir supprimé un préréglage dans Bambu Studio ou Bambu Handy)',
|
||||
allPresetsRequired: 'Tous les préréglages doivent être sélectionnés',
|
||||
useEmbedded: 'Utiliser les réglages intégrés du fichier',
|
||||
useEmbeddedHint: "Slicer tel que le concepteur l'a configuré (parois, remplissage, filament) au lieu des profils ci-dessus. Proposé car votre imprimante correspond à celle du fichier.",
|
||||
enqueuing: 'Envoi du travail de découpage…',
|
||||
queued: 'En file d\'attente…',
|
||||
failed: 'Échec du découpage. Vérifiez les journaux du sidecar.',
|
||||
|
||||
@@ -4020,6 +4020,8 @@ export default {
|
||||
refreshPresets: 'Aggiorna',
|
||||
refreshPresetsTitle: 'Aggiorna i preset — recupera gli elenchi più recenti dal cloud e dai bundle (da usare dopo aver eliminato un preset in Bambu Studio o Bambu Handy)',
|
||||
allPresetsRequired: 'Tutti i preset devono essere selezionati',
|
||||
useEmbedded: 'Usa le impostazioni integrate del file',
|
||||
useEmbeddedHint: 'Slicia come impostato dal designer (pareti, riempimento, filamento) invece dei profili sopra. Disponibile perché la tua stampante corrisponde a quella del file.',
|
||||
enqueuing: 'Invio lavoro di slicing…',
|
||||
queued: 'In coda…',
|
||||
failed: 'Slicing fallito. Controlla i log del sidecar.',
|
||||
|
||||
@@ -4032,6 +4032,8 @@ export default {
|
||||
refreshPresets: '再読み込み',
|
||||
refreshPresetsTitle: 'プリセットを再取得 — クラウドとバンドルの最新リストを取得します(Bambu Studio または Bambu Handy でプリセットを削除した後にお使いください)',
|
||||
allPresetsRequired: 'すべてのプリセットを選択する必要があります',
|
||||
useEmbedded: 'ファイルに埋め込まれた設定を使用',
|
||||
useEmbeddedHint: '上のプロファイルではなく、設計者が設定したとおり(ウォール、インフィル、フィラメント)にスライスします。お使いのプリンターがファイルと一致するため利用できます。',
|
||||
enqueuing: 'スライスジョブを送信中…',
|
||||
queued: '待機中…',
|
||||
failed: 'スライスに失敗。サイドカーのログを確認してください。',
|
||||
|
||||
@@ -3821,6 +3821,8 @@ export default {
|
||||
refreshPresets: '새로 고침',
|
||||
refreshPresetsTitle: '프리셋 새로 고침 — 최신 클라우드 및 번들 목록 가져오기 (Bambu Studio 또는 Bambu Handy에서 프리셋 삭제 후 사용)',
|
||||
allPresetsRequired: '모든 프리셋을 선택해야 합니다',
|
||||
useEmbedded: '파일에 포함된 설정 사용',
|
||||
useEmbeddedHint: '위 프로필 대신 디자이너가 설정한 대로(벽, 내부 채움, 필라멘트) 슬라이싱합니다. 프린터가 파일과 일치하여 사용할 수 있습니다.',
|
||||
enqueuing: '슬라이싱 작업 제출 중…',
|
||||
queued: '대기 중…',
|
||||
failed: '슬라이싱 실패. 슬라이서 사이드카 로그를 확인하세요.',
|
||||
|
||||
@@ -4020,6 +4020,8 @@ export default {
|
||||
refreshPresets: 'Atualizar',
|
||||
refreshPresetsTitle: 'Atualizar predefinições — buscar as listagens mais recentes da nuvem e dos pacotes (use após excluir uma predefinição no Bambu Studio ou Bambu Handy)',
|
||||
allPresetsRequired: 'Todas as predefinições devem ser selecionadas',
|
||||
useEmbedded: 'Usar as configurações incorporadas do arquivo',
|
||||
useEmbeddedHint: 'Fatiar como o designer configurou (paredes, preenchimento, filamento) em vez dos perfis acima. Disponível porque sua impressora corresponde à do arquivo.',
|
||||
enqueuing: 'Enviando trabalho de fatiamento…',
|
||||
queued: 'Na fila…',
|
||||
failed: 'Falha ao fatiar. Verifique os logs do sidecar.',
|
||||
|
||||
@@ -4022,6 +4022,8 @@ export default {
|
||||
refreshPresets: 'Yenile',
|
||||
refreshPresetsTitle: "Ön ayarları yenile — en güncel bulut ve paketli listeleri getir (Bambu Studio veya Bambu Handy'de bir ön ayar sildikten sonra kullanın)",
|
||||
allPresetsRequired: 'Tüm ön ayarlar seçilmelidir',
|
||||
useEmbedded: 'Dosyanın yerleşik ayarlarını kullan',
|
||||
useEmbeddedHint: 'Yukarıdaki profiller yerine tasarımcının ayarladığı gibi (duvarlar, dolgu, filament) dilimle. Yazıcınız dosyayla eşleştiği için sunuluyor.',
|
||||
enqueuing: 'Dilimleme işi gönderiliyor…',
|
||||
queued: 'Kuyrukta…',
|
||||
failed: 'Dilimleme başarısız. Dilimleyici yardımcı bileşen günlüklerini kontrol edin.',
|
||||
|
||||
@@ -4020,6 +4020,8 @@ export default {
|
||||
refreshPresets: '刷新',
|
||||
refreshPresetsTitle: '刷新预设 — 获取最新的云端和打包配置列表(在 Bambu Studio 或 Bambu Handy 中删除预设后使用)',
|
||||
allPresetsRequired: '必须选择所有预设',
|
||||
useEmbedded: '使用文件的内置设置',
|
||||
useEmbeddedHint: '按设计者的设置(壁、填充、耗材)切片,而非上方的配置文件。因您的打印机与文件匹配而可用。',
|
||||
enqueuing: '提交切片任务中…',
|
||||
queued: '已排队…',
|
||||
failed: '切片失败。请检查切片器 sidecar 日志。',
|
||||
|
||||
@@ -4020,6 +4020,8 @@ export default {
|
||||
refreshPresets: '重新整理',
|
||||
refreshPresetsTitle: '重新整理預設 — 擷取最新的雲端與打包設定清單(在 Bambu Studio 或 Bambu Handy 中刪除預設後使用)',
|
||||
allPresetsRequired: '必須選擇所有預設',
|
||||
useEmbedded: '使用檔案的內建設定',
|
||||
useEmbeddedHint: '依設計者的設定(外牆、填充、耗材)切片,而非上方的設定檔。因您的印表機與檔案相符而可用。',
|
||||
enqueuing: '提交切片任務中…',
|
||||
queued: '已排隊…',
|
||||
failed: '切片失敗。請檢查切片器 sidecar 日誌。',
|
||||
|
||||
Reference in New Issue
Block a user