diff --git a/packages/editor/src/components/editor/export-manager.tsx b/packages/editor/src/components/editor/export-manager.tsx index bbf97330..4bac49cb 100644 --- a/packages/editor/src/components/editor/export-manager.tsx +++ b/packages/editor/src/components/editor/export-manager.tsx @@ -4,13 +4,15 @@ import { useViewer } from '@pascal-app/viewer' import { useThree } from '@react-three/fiber' import { useEffect } from 'react' import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js' +import { STLExporter } from 'three/examples/jsm/exporters/STLExporter.js' +import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter.js' export function ExportManager() { const scene = useThree((state) => state.scene) const setExportScene = useViewer((state) => state.setExportScene) useEffect(() => { - const exportFn = async () => { + const exportFn = async (format: 'glb' | 'stl' | 'obj' = 'glb') => { // Find the scene renderer group by name const sceneGroup = scene.getObjectByName('scene-renderer') if (!sceneGroup) { @@ -18,20 +20,33 @@ export function ExportManager() { return } - const exporter = new GLTFExporter() const date = new Date().toISOString().split('T')[0] + if (format === 'stl') { + const exporter = new STLExporter() + const result = exporter.parse(sceneGroup, { binary: true }) + const blob = new Blob([result], { type: 'model/stl' }) + downloadBlob(blob, `model_${date}.stl`) + return + } + + if (format === 'obj') { + const exporter = new OBJExporter() + const result = exporter.parse(sceneGroup) + const blob = new Blob([result], { type: 'model/obj' }) + downloadBlob(blob, `model_${date}.obj`) + return + } + + // Default: GLB export (existing behavior) + const exporter = new GLTFExporter() + return new Promise((resolve, reject) => { exporter.parse( sceneGroup, (gltf) => { const blob = new Blob([gltf as ArrayBuffer], { type: 'model/gltf-binary' }) - const url = URL.createObjectURL(blob) - const link = document.createElement('a') - link.href = url - link.download = `model_${date}.glb` - link.click() - URL.revokeObjectURL(url) + downloadBlob(blob, `model_${date}.glb`) resolve() }, (error) => { @@ -52,3 +67,12 @@ export function ExportManager() { return null } + +function downloadBlob(blob: Blob, filename: string) { + const url = URL.createObjectURL(blob) + const link = document.createElement('a') + link.href = url + link.download = filename + link.click() + URL.revokeObjectURL(url) +} diff --git a/packages/editor/src/components/ui/sidebar/panels/settings-panel/index.tsx b/packages/editor/src/components/ui/sidebar/panels/settings-panel/index.tsx index 60151896..003ba21a 100644 --- a/packages/editor/src/components/ui/sidebar/panels/settings-panel/index.tsx +++ b/packages/editor/src/components/ui/sidebar/panels/settings-panel/index.tsx @@ -202,9 +202,9 @@ export function SettingsPanel({ const isLocalProject = false // Props-based; only show cloud sections when projectId provided - const handleExport = async () => { + const handleExport = async (format: 'glb' | 'stl' | 'obj' = 'glb') => { if (exportScene) { - await exportScene() + await exportScene(format) } } @@ -318,9 +318,17 @@ export function SettingsPanel({ {/* Export Section */}
- + +
diff --git a/packages/viewer/src/store/use-viewer.d.ts b/packages/viewer/src/store/use-viewer.d.ts index c06cbec6..80ea92cc 100644 --- a/packages/viewer/src/store/use-viewer.d.ts +++ b/packages/viewer/src/store/use-viewer.d.ts @@ -27,8 +27,8 @@ type ViewerState = { setSelection: (updates: Partial) => void resetSelection: () => void outliner: Outliner - exportScene: (() => Promise) | null - setExportScene: (fn: (() => Promise) | null) => void + exportScene: ((format?: 'glb' | 'stl' | 'obj') => Promise) | null + setExportScene: (fn: ((format?: 'glb' | 'stl' | 'obj') => Promise) | null) => void } declare const useViewer: import('zustand').UseBoundStore> export default useViewer diff --git a/packages/viewer/src/store/use-viewer.ts b/packages/viewer/src/store/use-viewer.ts index bd8dbc63..43d510ab 100644 --- a/packages/viewer/src/store/use-viewer.ts +++ b/packages/viewer/src/store/use-viewer.ts @@ -61,8 +61,8 @@ type ViewerState = { outliner: Outliner // No setter as we will manipulate directly the arrays // Export functionality - exportScene: (() => Promise) | null - setExportScene: (fn: (() => Promise) | null) => void + exportScene: ((format?: 'glb' | 'stl' | 'obj') => Promise) | null + setExportScene: (fn: ((format?: 'glb' | 'stl' | 'obj') => Promise) | null) => void debugColors: boolean setDebugColors: (enabled: boolean) => void