Skip to content

Commit

Permalink
add support for downloading gltf (#502)
Browse files Browse the repository at this point in the history
* add support for downloading gltf

* throw toast on gltf fail
  • Loading branch information
seveibar authored Jan 7, 2025
1 parent 2d8ca2c commit c0dcd9b
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 4 deletions.
17 changes: 15 additions & 2 deletions src/components/DownloadButtonAndMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { downloadKicadFiles } from "@/lib/download-fns/download-kicad-files"
import { AnyCircuitElement } from "circuit-json"
import { ChevronDown, Download } from "lucide-react"
import React from "react"
import { downloadGltf } from "@/lib/download-fns/download-gltf"

interface DownloadButtonAndMenuProps {
className?: string
Expand Down Expand Up @@ -67,12 +68,24 @@ export function DownloadButtonAndMenu({
</DropdownMenuItem>
<DropdownMenuItem
className="text-xs"
onClick={() => notImplemented("3d model downloads")}
onClick={async () => {
try {
await downloadGltf(
circuitJson,
snippetUnscopedName || "circuit",
)
} catch (error: any) {
toast({
title: "Error Downloading 3D Model",
description: error.toString(),
})
}
}}
>
<Download className="mr-1 h-3 w-3" />
<span className="flex-grow mr-6">Download 3D Model</span>
<span className="text-[0.6rem] bg-green-500 opacity-80 text-white font-mono rounded-md px-1 text-center py-0.5 mr-1">
stl
gltf
</span>
</DropdownMenuItem>
<DropdownMenuItem
Expand Down
16 changes: 14 additions & 2 deletions src/components/PreviewContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { applyPcbEditEvents } from "@/lib/utils/pcbManualEditEventHandler"
import { CadViewer } from "@tscircuit/3d-viewer"
import { PCBViewer } from "@tscircuit/pcb-viewer"
import { Schematic } from "@tscircuit/schematic-viewer"
import { useEffect, useState } from "react"
import { useEffect, useRef, useState } from "react"
import { ErrorFallback } from "./ErrorFallback"
import { ErrorBoundary } from "react-error-boundary"
import { ErrorTabContent } from "./ErrorTabContent"
Expand All @@ -29,6 +29,7 @@ import {
} from "./ui/dropdown-menu"
import { Button } from "./ui/button"
import { PcbViewerWithContainerHeight } from "./PcbViewerWithContainerHeight"
import { useGlobalStore } from "@/hooks/use-global-store"

export interface PreviewContentProps {
code: string
Expand All @@ -54,6 +55,12 @@ export interface PreviewContentProps {
onManualEditsFileContentChange?: (newmanualEditsFileContent: string) => void
}

declare global {
interface Window {
TSCIRCUIT_3D_OBJECT_REF: any
}
}

export const PreviewContent = ({
code,
triggerRunTsx,
Expand All @@ -79,6 +86,11 @@ export const PreviewContent = ({
}: PreviewContentProps) => {
const [activeTab, setActiveTab] = useState(showCodeTab ? "code" : "pcb")
const [lastRunHash, setLastRunHash] = useState("")
const threeJsObjectRef = useRef<any>(null)

useEffect(() => {
window.TSCIRCUIT_3D_OBJECT_REF = threeJsObjectRef
}, [])

const currentCodeHash = code + "\n" + manualEditsFileContent
const hasCodeChangedSinceLastRun = lastRunHash !== currentCodeHash
Expand Down Expand Up @@ -308,7 +320,7 @@ export const PreviewContent = ({
>
<ErrorBoundary FallbackComponent={ErrorFallback}>
{circuitJson ? (
<CadViewer soup={circuitJson as any} />
<CadViewer soup={circuitJson as any} ref={threeJsObjectRef} />
) : (
<PreviewEmptyState triggerRunTsx={triggerRunTsx} />
)}
Expand Down
49 changes: 49 additions & 0 deletions src/lib/download-fns/download-gltf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { AnyCircuitElement } from "circuit-json"
import { convertCircuitJsonToAssemblySvg } from "circuit-to-svg"
import { GLTFExporter, type GLTFExporterOptions } from "three-stdlib"
import { saveAs } from "file-saver"
import * as THREE from "three"

export const downloadGltf = async (
circuitJson: AnyCircuitElement[],
fileName: string,
) => {
const threeJsObject = window.TSCIRCUIT_3D_OBJECT_REF
?.current as THREE.Object3D

if (!threeJsObject) {
throw new Error(
"No 3D object found, run the snippet before downloading the 3d model",
)
}

const exporter = new GLTFExporter()

const options: GLTFExporterOptions = {
binary: true,
}

const {
promise: gltfPromise,
resolve: resolveGltf,
reject: rejectGltf,
} = Promise.withResolvers<Blob>()

exporter.parse(
threeJsObject,
(gltf) => {
const type = options.binary ? "gltf-binary" : "gltf+json"
const blob = new Blob(
[gltf instanceof ArrayBuffer ? gltf : JSON.stringify(gltf)],
{ type: `model/${type}` },
)
resolveGltf(blob)
},
rejectGltf,
options,
)

const gltfBlob = await gltfPromise

saveAs(gltfBlob, fileName + ".gltf")
}

0 comments on commit c0dcd9b

Please sign in to comment.