From ca1433a050e464618d6d734d06d4d7031237bd77 Mon Sep 17 00:00:00 2001 From: Amit Date: Fri, 28 Aug 2026 13:51:33 +0530 Subject: [PATCH 1/2] feat(editor): expose printable STL export --- .../print-export-button.test.ts | 27 +++++++++++++++++ .../settings-panel/print-export-button.tsx | 29 +++++++++++++++---- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts b/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts index 26fedea1b9..d028a6caf2 100644 --- a/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts +++ b/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts @@ -58,6 +58,33 @@ describe('simple 3D print export', () => { expect(prepared).toEqual({ artifact, report }) }) + test('uses the same safe profile for printable STL files', async () => { + const calls: { format?: string; options?: ModelExportOptions }[] = [] + const stlReport: PrintExportReport = { ...report, format: 'stl' } + const artifact = { blob: new Blob(['stl']), filename: 'house.zip', metadata: stlReport } + const modelExport: ModelExport = async (format, options) => { + calls.push({ format, options }) + return artifact + } + + const prepared = await preparePrintExport(modelExport, false, 'print-stl') + + expect(calls).toEqual([ + { + format: 'print-stl', + options: { + onlyVisible: false, + download: false, + printScale: 100, + printScope: 'levels', + printContent: 'structure', + printBase: 'none', + }, + }, + ]) + expect(prepared).toEqual({ artifact, report: stlReport }) + }) + test('blocks the download when preflight finds invalid geometry', async () => { const blockedReport: PrintExportReport = { ...report, diff --git a/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx b/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx index b7298edb5e..93e99bd885 100644 --- a/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +++ b/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx @@ -5,7 +5,11 @@ import { isPrintLevelBundleReport, type PrintLevelBundleReport, } from '../../../../../lib/level-print-export' -import type { ModelExport, ModelExportArtifact } from '../../../../../lib/model-export' +import type { + ModelExport, + ModelExportArtifact, + ModelExportFormat, +} from '../../../../../lib/model-export' import { isPrintExportReport, type PrintExportReport, @@ -17,6 +21,8 @@ type PreparedPrintExport = { report: PrintExportReport | PrintLevelBundleReport } +type PrintModelExportFormat = Extract + function downloadArtifact(artifact: ModelExportArtifact) { const url = URL.createObjectURL(artifact.blob) const link = document.createElement('a') @@ -39,8 +45,9 @@ function firstBlockingMessage(report: PrintExportReport | PrintLevelBundleReport export async function preparePrintExport( modelExport: ModelExport, onlyVisible: boolean, + format: PrintModelExportFormat = 'print-3mf', ): Promise { - const artifact = await modelExport('print-3mf', { + const artifact = await modelExport(format, { onlyVisible, download: false, printScale: 100, @@ -71,13 +78,13 @@ export function PrintExportButton({ onlyVisible }: { onlyVisible: boolean }) { const [isExporting, setIsExporting] = useState(false) const [error, setError] = useState(null) - const handleExport = async () => { + const handleExport = async (format: PrintModelExportFormat) => { if (!modelExport) return setIsExporting(true) setError(null) try { - const prepared = await preparePrintExport(modelExport, onlyVisible) + const prepared = await preparePrintExport(modelExport, onlyVisible, format) downloadArtifact(prepared.artifact) } catch (reason) { setError(reason instanceof Error ? reason.message : '3D print export failed.') @@ -92,11 +99,21 @@ export function PrintExportButton({ onlyVisible }: { onlyVisible: boolean }) { aria-busy={isExporting} className="w-full justify-start gap-2" disabled={isExporting || !modelExport} - onClick={handleExport} + onClick={() => void handleExport('print-3mf')} + variant="outline" + > + + Export 3D print 3MF + + {error && (
From e67e701cce589141329841f0c3b588e453248d87 Mon Sep 17 00:00:00 2001 From: Amit Date: Fri, 4 Sep 2026 10:29:41 +0530 Subject: [PATCH 2/2] fix(editor): address print STL export review feedback Track in-flight format for aria-busy, align button labels, and make preparePrintExport format required. Co-authored-by: Cursor --- .../settings-panel/print-export-button.test.ts | 8 ++++---- .../settings-panel/print-export-button.tsx | 16 +++++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts b/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts index d028a6caf2..b6b016e334 100644 --- a/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts +++ b/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.test.ts @@ -40,7 +40,7 @@ describe('simple 3D print export', () => { return artifact } - const prepared = await preparePrintExport(modelExport, true) + const prepared = await preparePrintExport(modelExport, true, 'print-3mf') expect(calls).toEqual([ { @@ -103,7 +103,7 @@ describe('simple 3D print export', () => { metadata: blockedReport, }) - await expect(preparePrintExport(modelExport, true)).rejects.toThrow( + await expect(preparePrintExport(modelExport, true, 'print-3mf')).rejects.toThrow( 'One wall has an open edge.', ) }) @@ -149,7 +149,7 @@ describe('simple 3D print export', () => { metadata: blockedBundleReport, }) - await expect(preparePrintExport(modelExport, true)).rejects.toThrow( + await expect(preparePrintExport(modelExport, true, 'print-3mf')).rejects.toThrow( 'The upper level has an open edge.', ) }) @@ -160,7 +160,7 @@ describe('simple 3D print export', () => { filename: 'house.3mf', }) - await expect(preparePrintExport(modelExport, false)).rejects.toThrow( + await expect(preparePrintExport(modelExport, false, 'print-3mf')).rejects.toThrow( 'did not return a valid file', ) }) diff --git a/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx b/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx index 93e99bd885..eb7cd0d8ef 100644 --- a/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx +++ b/packages/editor/src/components/ui/sidebar/panels/settings-panel/print-export-button.tsx @@ -45,7 +45,7 @@ function firstBlockingMessage(report: PrintExportReport | PrintLevelBundleReport export async function preparePrintExport( modelExport: ModelExport, onlyVisible: boolean, - format: PrintModelExportFormat = 'print-3mf', + format: PrintModelExportFormat, ): Promise { const artifact = await modelExport(format, { onlyVisible, @@ -75,13 +75,13 @@ export async function preparePrintExport( export function PrintExportButton({ onlyVisible }: { onlyVisible: boolean }) { const modelExport = useEditor((state) => state.modelExport) - const [isExporting, setIsExporting] = useState(false) + const [exportingFormat, setExportingFormat] = useState(null) const [error, setError] = useState(null) const handleExport = async (format: PrintModelExportFormat) => { if (!modelExport) return - setIsExporting(true) + setExportingFormat(format) setError(null) try { const prepared = await preparePrintExport(modelExport, onlyVisible, format) @@ -89,14 +89,16 @@ export function PrintExportButton({ onlyVisible }: { onlyVisible: boolean }) { } catch (reason) { setError(reason instanceof Error ? reason.message : '3D print export failed.') } finally { - setIsExporting(false) + setExportingFormat(null) } } + const isExporting = exportingFormat !== null + return ( <> {error && (