diff --git a/src/state/exportStore.ts b/src/state/exportStore.ts new file mode 100644 index 0000000..c11d1cd --- /dev/null +++ b/src/state/exportStore.ts @@ -0,0 +1,12 @@ +import { create } from "zustand"; + +type ActionStore = { + action: boolean; + + setAction: (action: boolean) => void; +}; + +export const useActionStore = create((set) => ({ + action: false, + setAction: (action) => set(() => ({ action: action })), +})); diff --git a/src/three/Space.tsx b/src/three/Space.tsx index 50ba3c8..9e0d311 100644 --- a/src/three/Space.tsx +++ b/src/three/Space.tsx @@ -1,8 +1,13 @@ -import React, { useState } from "react"; -import { Canvas } from "@react-three/fiber"; +import React, { useEffect, useState } from "react"; +import { Canvas, useThree } from "@react-three/fiber"; import { useAreaStore } from "@/state/areaStore"; import { OrbitControls, Html } from "@react-three/drei"; import * as THREE from "three"; +import { NextButton } from "@/components/button/BottomButton"; +import { ChevronRight } from "lucide-react"; +import { css } from "@emotion/react"; +import { useActionStore } from "@/state/exportStore"; +import { GLTFExporter } from "three/examples/jsm/Addons.js"; function Building({ shape, @@ -121,7 +126,7 @@ export function Space() { const buildingsData = areaData(); return ( - + + ); } -function Floor() { - return ( - - - - - ); +// function Floor() { +// return ( +// +// +// +// +// ); +// } + +export function Export() { + const { scene } = useThree(); + + const action = useActionStore((state) => state.action); + const setAction = useActionStore((state) => state.setAction); + + useEffect(() => { + if (action == true) { + setAction(false); + console.log(action); + exportGLB(); + } + }, [action]); + + const exportGLB = () => { + const sceneClone = scene.clone(true); + sceneClone.traverse((child) => { + if (child.userData && child.userData.skipExport === true) { + if (child.parent) child.parent.remove(child); + } + }); + + const exporter = new GLTFExporter(); + const options = { + binary: true, + embedImages: true, + }; + exporter.parse( + sceneClone, + (result) => { + if (result instanceof ArrayBuffer) { + const blob = new Blob([result], { type: "model/gltf-binary" }); + const link = document.createElement("a"); + link.style.display = "none"; + document.body.appendChild(link); + link.href = URL.createObjectURL(blob); + link.download = "scene.glb"; + link.click(); + document.body.removeChild(link); + } else { + console.error("GLB export failed: unexpected result", result); + } + }, + function () { + console.log("An error happened"); + }, + options + ); + }; + + return <>; } diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 5232e7f..6d46f94 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -1,5 +1,5 @@ import { css } from "@emotion/react"; -import { Space } from "../three/Space"; +import { ExportButton, Space } from "../three/Space"; import { FullscreenModal } from "../components/FullscreenModal"; import { Title } from "@/components/text/Title"; import { Description } from "@/components/text/Description"; @@ -8,8 +8,9 @@ import { MapComponent } from "@/components/map/SelectMap"; import { useState } from "react"; import { NextButton, PrevButton } from "@/components/button/BottomButton"; import { BuildingHeights } from "@/components/map/Processing"; -import { ChevronLeft, ChevronRight } from "lucide-react"; +import { ChevronLeft, ChevronRight, Download } from "lucide-react"; import { useAreaStore } from "@/state/areaStore"; +import { useActionStore } from "@/state/exportStore"; const IconSize = css({ width: "14px", @@ -22,6 +23,7 @@ function App() { const [steps, setSteps] = useState(["front", "processing"]); const [step, setStep] = useState(0); const setCenter = useAreaStore((state) => state.setCenter); + const setAction = useActionStore((state) => state.setAction); const handleDone = (data) => { setAreaData(data); @@ -43,6 +45,10 @@ function App() { setStep(step - 1); }; + const handleClickExport = () => { + setAction(true); + }; + return (
@@ -78,14 +84,18 @@ function App() { Next Step - + + Export GLB + + +
); }