2023-08-02 16:23:17 -07:00
|
|
|
import { v4 as uuidv4 } from 'uuid'
|
2023-08-18 10:27:01 -04:00
|
|
|
import { faFileExport, faXmark } from '@fortawesome/free-solid-svg-icons'
|
2023-08-02 16:23:17 -07:00
|
|
|
import { ActionButton } from './ActionButton'
|
|
|
|
import Modal from 'react-modal'
|
|
|
|
import React from 'react'
|
|
|
|
import { useFormik } from 'formik'
|
|
|
|
import { Models } from '@kittycad/lib'
|
2023-09-25 19:49:53 -07:00
|
|
|
import { engineCommandManager } from '../lang/std/engineConnection'
|
2023-09-27 15:42:16 -07:00
|
|
|
import { useGlobalStateContext } from 'hooks/useGlobalStateContext'
|
2023-08-02 16:23:17 -07:00
|
|
|
|
|
|
|
type OutputFormat = Models['OutputFormat_type']
|
2023-09-27 15:42:16 -07:00
|
|
|
type OutputTypeKey = OutputFormat['type']
|
|
|
|
type ExtractStorageTypes<T> = T extends { storage: infer U } ? U : never
|
|
|
|
type StorageUnion = ExtractStorageTypes<OutputFormat>
|
2023-08-02 16:23:17 -07:00
|
|
|
|
2023-08-18 10:27:01 -04:00
|
|
|
interface ExportButtonProps extends React.PropsWithChildren {
|
|
|
|
className?: {
|
|
|
|
button?: string
|
2023-10-16 13:28:41 -04:00
|
|
|
icon?: string
|
|
|
|
bg?: string
|
2023-08-18 10:27:01 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export const ExportButton = ({ children, className }: ExportButtonProps) => {
|
2023-08-02 16:23:17 -07:00
|
|
|
const [modalIsOpen, setIsOpen] = React.useState(false)
|
2023-09-27 15:42:16 -07:00
|
|
|
const {
|
|
|
|
settings: {
|
|
|
|
state: {
|
|
|
|
context: { baseUnit },
|
|
|
|
},
|
|
|
|
},
|
|
|
|
} = useGlobalStateContext()
|
2023-08-02 16:23:17 -07:00
|
|
|
|
|
|
|
const defaultType = 'gltf'
|
2023-09-27 15:42:16 -07:00
|
|
|
const [type, setType] = React.useState<OutputTypeKey>(defaultType)
|
|
|
|
const defaultStorage = 'embedded'
|
|
|
|
const [storage, setStorage] = React.useState<StorageUnion>(defaultStorage)
|
2023-08-02 16:23:17 -07:00
|
|
|
|
|
|
|
function openModal() {
|
|
|
|
setIsOpen(true)
|
|
|
|
}
|
|
|
|
|
|
|
|
function closeModal() {
|
|
|
|
setIsOpen(false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Default to gltf and embedded.
|
|
|
|
const initialValues: OutputFormat = {
|
|
|
|
type: defaultType,
|
2023-09-27 15:42:16 -07:00
|
|
|
storage: defaultStorage,
|
2023-09-05 16:02:27 -07:00
|
|
|
presentation: 'pretty',
|
2023-08-02 16:23:17 -07:00
|
|
|
}
|
|
|
|
const formik = useFormik({
|
|
|
|
initialValues,
|
|
|
|
onSubmit: (values: OutputFormat) => {
|
|
|
|
// Set the default coords.
|
|
|
|
if (
|
|
|
|
values.type === 'obj' ||
|
|
|
|
values.type === 'ply' ||
|
|
|
|
values.type === 'step' ||
|
|
|
|
values.type === 'stl'
|
|
|
|
) {
|
|
|
|
// Set the default coords.
|
|
|
|
// In the future we can make this configurable.
|
|
|
|
// But for now, its probably best to keep it consistent with the
|
|
|
|
// UI.
|
|
|
|
values.coords = {
|
|
|
|
forward: {
|
|
|
|
axis: 'y',
|
|
|
|
direction: 'negative',
|
|
|
|
},
|
|
|
|
up: {
|
|
|
|
axis: 'z',
|
|
|
|
direction: 'positive',
|
|
|
|
},
|
|
|
|
}
|
|
|
|
}
|
2023-11-29 13:24:09 +11:00
|
|
|
if (
|
|
|
|
values.type === 'obj' ||
|
|
|
|
values.type === 'stl' ||
|
|
|
|
values.type === 'ply'
|
|
|
|
) {
|
2023-09-27 15:42:16 -07:00
|
|
|
values.units = baseUnit
|
|
|
|
}
|
|
|
|
if (
|
|
|
|
values.type === 'ply' ||
|
|
|
|
values.type === 'stl' ||
|
|
|
|
values.type === 'gltf'
|
|
|
|
) {
|
|
|
|
// Set the storage type.
|
|
|
|
values.storage = storage
|
|
|
|
}
|
2023-11-29 13:24:09 +11:00
|
|
|
if (values.type === 'ply' || values.type === 'stl') {
|
|
|
|
values.selection = { type: 'default_scene' }
|
|
|
|
}
|
2023-09-25 19:49:53 -07:00
|
|
|
engineCommandManager.sendSceneCommand({
|
2023-08-02 16:23:17 -07:00
|
|
|
type: 'modeling_cmd_req',
|
|
|
|
cmd: {
|
|
|
|
type: 'export',
|
|
|
|
// By default let's leave this blank to export the whole scene.
|
|
|
|
// In the future we might want to let the user choose which entities
|
|
|
|
// in the scene to export. In that case, you'd pass the IDs thru here.
|
|
|
|
entity_ids: [],
|
|
|
|
format: values,
|
2023-09-27 15:42:16 -07:00
|
|
|
source_unit: baseUnit,
|
2023-08-02 16:23:17 -07:00
|
|
|
},
|
|
|
|
cmd_id: uuidv4(),
|
|
|
|
})
|
|
|
|
|
|
|
|
closeModal()
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
return (
|
|
|
|
<>
|
2023-08-18 10:27:01 -04:00
|
|
|
<ActionButton
|
|
|
|
onClick={openModal}
|
|
|
|
Element="button"
|
2023-10-16 13:28:41 -04:00
|
|
|
icon={{
|
|
|
|
icon: faFileExport,
|
|
|
|
iconClassName: className?.icon,
|
|
|
|
bgClassName: className?.bg,
|
|
|
|
}}
|
2023-08-18 10:27:01 -04:00
|
|
|
className={className?.button}
|
|
|
|
>
|
|
|
|
{children || 'Export'}
|
|
|
|
</ActionButton>
|
2023-08-02 16:23:17 -07:00
|
|
|
<Modal
|
|
|
|
isOpen={modalIsOpen}
|
|
|
|
onRequestClose={closeModal}
|
|
|
|
contentLabel="Export"
|
2023-08-18 10:27:01 -04:00
|
|
|
overlayClassName="z-40 fixed inset-0 grid place-items-center"
|
|
|
|
className="rounded p-4 bg-chalkboard-10 dark:bg-chalkboard-100 border max-w-xl w-full"
|
2023-08-02 16:23:17 -07:00
|
|
|
>
|
2023-08-18 10:27:01 -04:00
|
|
|
<h1 className="text-2xl font-bold">Export your design</h1>
|
|
|
|
<form onSubmit={formik.handleSubmit}>
|
|
|
|
<div className="flex flex-wrap justify-between gap-8 items-center w-full my-8">
|
|
|
|
<label htmlFor="type" className="flex-1">
|
|
|
|
<p className="mb-2">Type</p>
|
2023-08-02 16:23:17 -07:00
|
|
|
<select
|
|
|
|
id="type"
|
|
|
|
name="type"
|
2023-11-29 11:20:23 +11:00
|
|
|
data-testid="export-type"
|
2023-08-02 16:23:17 -07:00
|
|
|
onChange={(e) => {
|
2023-09-27 15:42:16 -07:00
|
|
|
setType(e.target.value as OutputTypeKey)
|
|
|
|
if (e.target.value === 'gltf') {
|
|
|
|
// Set default to embedded.
|
|
|
|
setStorage('embedded')
|
|
|
|
} else if (e.target.value === 'ply') {
|
|
|
|
// Set default to ascii.
|
|
|
|
setStorage('ascii')
|
|
|
|
} else if (e.target.value === 'stl') {
|
|
|
|
// Set default to ascii.
|
|
|
|
setStorage('ascii')
|
|
|
|
}
|
2023-08-02 16:23:17 -07:00
|
|
|
formik.handleChange(e)
|
|
|
|
}}
|
2023-08-18 10:27:01 -04:00
|
|
|
className="bg-chalkboard-20 dark:bg-chalkboard-90 w-full"
|
2023-08-02 16:23:17 -07:00
|
|
|
>
|
|
|
|
<option value="gltf">gltf</option>
|
|
|
|
<option value="obj">obj</option>
|
|
|
|
<option value="ply">ply</option>
|
|
|
|
<option value="step">step</option>
|
|
|
|
<option value="stl">stl</option>
|
|
|
|
</select>
|
2023-08-18 10:27:01 -04:00
|
|
|
</label>
|
2023-08-02 16:23:17 -07:00
|
|
|
{(type === 'gltf' || type === 'ply' || type === 'stl') && (
|
2023-08-18 10:27:01 -04:00
|
|
|
<label htmlFor="storage" className="flex-1">
|
|
|
|
<p className="mb-2">Storage</p>
|
|
|
|
<select
|
|
|
|
id="storage"
|
|
|
|
name="storage"
|
2023-11-29 11:20:23 +11:00
|
|
|
data-testid="export-storage"
|
2023-09-27 15:42:16 -07:00
|
|
|
onChange={(e) => {
|
|
|
|
setStorage(e.target.value as StorageUnion)
|
|
|
|
formik.handleChange(e)
|
|
|
|
}}
|
2023-08-18 10:27:01 -04:00
|
|
|
className="bg-chalkboard-20 dark:bg-chalkboard-90 w-full"
|
|
|
|
>
|
|
|
|
{type === 'gltf' && (
|
|
|
|
<>
|
|
|
|
<option value="embedded">embedded</option>
|
|
|
|
<option value="binary">binary</option>
|
|
|
|
<option value="standard">standard</option>
|
|
|
|
</>
|
|
|
|
)}
|
2023-11-29 13:24:09 +11:00
|
|
|
{type === 'stl' && (
|
2023-08-18 10:27:01 -04:00
|
|
|
<>
|
|
|
|
<option value="ascii">ascii</option>
|
|
|
|
<option value="binary">binary</option>
|
|
|
|
</>
|
|
|
|
)}
|
2023-11-29 13:24:09 +11:00
|
|
|
{type === 'ply' && (
|
2023-08-18 10:27:01 -04:00
|
|
|
<>
|
|
|
|
<option value="ascii">ascii</option>
|
|
|
|
<option value="binary_little_endian">
|
|
|
|
binary_little_endian
|
|
|
|
</option>
|
|
|
|
<option value="binary_big_endian">
|
|
|
|
binary_big_endian
|
|
|
|
</option>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</select>
|
|
|
|
</label>
|
2023-08-02 16:23:17 -07:00
|
|
|
)}
|
2023-08-18 10:27:01 -04:00
|
|
|
</div>
|
2023-08-02 16:23:17 -07:00
|
|
|
|
|
|
|
<div className="flex justify-between mt-6">
|
|
|
|
<ActionButton
|
2023-08-15 21:56:24 -04:00
|
|
|
Element="button"
|
2023-08-02 16:23:17 -07:00
|
|
|
onClick={closeModal}
|
|
|
|
icon={{
|
|
|
|
icon: faXmark,
|
|
|
|
bgClassName: 'bg-destroy-80',
|
|
|
|
iconClassName:
|
|
|
|
'text-destroy-20 group-hover:text-destroy-10 hover:text-destroy-10',
|
|
|
|
}}
|
|
|
|
className="hover:border-destroy-40"
|
|
|
|
>
|
|
|
|
Close
|
|
|
|
</ActionButton>
|
2023-08-18 10:27:01 -04:00
|
|
|
<ActionButton
|
|
|
|
Element="button"
|
|
|
|
type="submit"
|
|
|
|
icon={{ icon: faFileExport }}
|
|
|
|
>
|
|
|
|
Export
|
|
|
|
</ActionButton>
|
2023-08-02 16:23:17 -07:00
|
|
|
</div>
|
2023-08-18 10:27:01 -04:00
|
|
|
</form>
|
2023-08-02 16:23:17 -07:00
|
|
|
</Modal>
|
|
|
|
</>
|
|
|
|
)
|
|
|
|
}
|