Merge pull request #203 from thiago-lopes-dev/main
feat: implement export scene system (GLB, STL, OBJ)
This commit is contained in:
@@ -14,6 +14,7 @@ import { Canvas, extend, type ThreeToJSXElements, useFrame, useThree } from '@re
|
|||||||
import { useEffect, useMemo, useRef } from 'react'
|
import { useEffect, useMemo, useRef } from 'react'
|
||||||
import * as THREE from 'three/webgpu'
|
import * as THREE from 'three/webgpu'
|
||||||
import useViewer from '../../store/use-viewer'
|
import useViewer from '../../store/use-viewer'
|
||||||
|
import { ExportSystem } from '../../systems/export/export-system'
|
||||||
import { GuideSystem } from '../../systems/guide/guide-system'
|
import { GuideSystem } from '../../systems/guide/guide-system'
|
||||||
import { ItemLightSystem } from '../../systems/item-light/item-light-system'
|
import { ItemLightSystem } from '../../systems/item-light/item-light-system'
|
||||||
import { LevelSystem } from '../../systems/level/level-system'
|
import { LevelSystem } from '../../systems/level/level-system'
|
||||||
@@ -144,6 +145,7 @@ const Viewer: React.FC<ViewerProps> = ({
|
|||||||
<WallSystem />
|
<WallSystem />
|
||||||
<WindowSystem />
|
<WindowSystem />
|
||||||
<ZoneSystem />
|
<ZoneSystem />
|
||||||
|
<ExportSystem />
|
||||||
<PostProcessing />
|
<PostProcessing />
|
||||||
{/* <DebugRenderer /> */}
|
{/* <DebugRenderer /> */}
|
||||||
<GPUDeviceWatcher />
|
<GPUDeviceWatcher />
|
||||||
|
|||||||
@@ -14,5 +14,6 @@ export {
|
|||||||
disposeMaterial,
|
disposeMaterial,
|
||||||
} from './lib/materials'
|
} from './lib/materials'
|
||||||
export { default as useViewer } from './store/use-viewer'
|
export { default as useViewer } from './store/use-viewer'
|
||||||
|
export { ExportSystem } from './systems/export/export-system'
|
||||||
export { InteractiveSystem } from './systems/interactive/interactive-system'
|
export { InteractiveSystem } from './systems/interactive/interactive-system'
|
||||||
export { snapLevelsToTruePositions } from './systems/level/level-utils'
|
export { snapLevelsToTruePositions } from './systems/level/level-utils'
|
||||||
|
|||||||
@@ -0,0 +1,71 @@
|
|||||||
|
'use client'
|
||||||
|
|
||||||
|
import { useThree } from '@react-three/fiber'
|
||||||
|
import { useEffect } from 'react'
|
||||||
|
import { STLExporter } from 'three/examples/jsm/exporters/STLExporter.js'
|
||||||
|
import { GLTFExporter } from 'three/examples/jsm/exporters/GLTFExporter.js'
|
||||||
|
import { OBJExporter } from 'three/examples/jsm/exporters/OBJExporter.js'
|
||||||
|
import useViewer from '../../store/use-viewer'
|
||||||
|
|
||||||
|
const EDITOR_LAYER = 1 // same constant used across the editor
|
||||||
|
|
||||||
|
function downloadBlob(blob: Blob, filename: string) {
|
||||||
|
const url = URL.createObjectURL(blob)
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = url
|
||||||
|
a.download = filename
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
document.body.removeChild(a)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ExportSystem = () => {
|
||||||
|
const { scene } = useThree()
|
||||||
|
const setExportScene = useViewer((state) => state.setExportScene)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const exportFn = async (format: 'glb' | 'stl' | 'obj' = 'glb') => {
|
||||||
|
const date = new Date().toISOString().split('T')[0]
|
||||||
|
const filename = `pascal-export-${date}`
|
||||||
|
|
||||||
|
// Clone scene and strip editor-only objects (layer 1 = EDITOR_LAYER)
|
||||||
|
const exportRoot = scene.clone(true)
|
||||||
|
const toRemove: typeof exportRoot[] = []
|
||||||
|
exportRoot.traverse((obj) => {
|
||||||
|
if (obj.layers.isEnabled(EDITOR_LAYER)) {
|
||||||
|
toRemove.push(obj)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
for (const obj of toRemove) {
|
||||||
|
obj.parent?.remove(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (format === 'glb') {
|
||||||
|
const exporter = new GLTFExporter()
|
||||||
|
const result = await new Promise<ArrayBuffer>((resolve, reject) => {
|
||||||
|
exporter.parse(
|
||||||
|
exportRoot,
|
||||||
|
(output) => resolve(output as ArrayBuffer),
|
||||||
|
(err) => reject(err),
|
||||||
|
{ binary: true }
|
||||||
|
)
|
||||||
|
})
|
||||||
|
downloadBlob(new Blob([result], { type: 'model/gltf-binary' }), `${filename}.glb`)
|
||||||
|
} else if (format === 'stl') {
|
||||||
|
const exporter = new STLExporter()
|
||||||
|
const result = exporter.parse(exportRoot, { binary: true }) as DataView
|
||||||
|
downloadBlob(new Blob([result], { type: 'model/stl' }), `${filename}.stl`)
|
||||||
|
} else if (format === 'obj') {
|
||||||
|
const exporter = new OBJExporter()
|
||||||
|
const result = exporter.parse(exportRoot)
|
||||||
|
downloadBlob(new Blob([result], { type: 'model/obj' }), `${filename}.obj`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setExportScene(exportFn)
|
||||||
|
return () => setExportScene(null)
|
||||||
|
}, [scene, setExportScene])
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user