diff --git a/examples/r3f-cra/src/App.js b/examples/r3f-cra/src/App.js index b707c2a..dc3ef8f 100644 --- a/examples/r3f-cra/src/App.js +++ b/examples/r3f-cra/src/App.js @@ -92,7 +92,7 @@ function App() { shadowMap > getProject('Playground - R3F').sheet('R3F-Canvas')} + sheet={getProject('Playground - R3F').sheet('R3F-Canvas')} > {/* @ts-ignore */} diff --git a/packages/playground/src/shared/instances/App.tsx b/packages/playground/src/shared/instances/App.tsx new file mode 100644 index 0000000..a95e6cd --- /dev/null +++ b/packages/playground/src/shared/instances/App.tsx @@ -0,0 +1,109 @@ +import {editable as e, RefreshSnapshot, SheetProvider} from '@theatre/r3f' +import {Stars} from '@react-three/drei' +import {getProject} from '@theatre/core' +import React, {Suspense, useState} from 'react' +import {Canvas} from '@react-three/fiber' +import {useGLTF, PerspectiveCamera} from '@react-three/drei' +import sceneGLB from './scene.glb' + +document.body.style.backgroundColor = '#171717' + +const EditableCamera = e(PerspectiveCamera, 'perspectiveCamera') + +function Model({ + url, + instance, + ...props +}: {url: string; instance?: string} & JSX.IntrinsicElements['group']) { + const {nodes} = useGLTF(url) as any + + return ( + + + + + + + + + + + + ) +} + +function App() { + const bgs = ['#272730', '#b7c5d1'] + const [bgIndex, setBgIndex] = useState(0) + const bg = bgs[bgIndex] + return ( +
{ + // return setBgIndex((bgIndex) => (bgIndex + 1) % bgs.length) + }} + style={{ + height: '100vh', + }} + > + + + + + + + + + + + + + + + + + + +
+ ) +} + +export default App diff --git a/packages/playground/src/shared/instances/index.tsx b/packages/playground/src/shared/instances/index.tsx new file mode 100644 index 0000000..0d0a27b --- /dev/null +++ b/packages/playground/src/shared/instances/index.tsx @@ -0,0 +1,15 @@ +import React from 'react' +import ReactDOM from 'react-dom' +import App from './App' +import studio from '@theatre/studio' +import {extension} from '@theatre/r3f' + +studio.extend(extension) +studio.initialize() + +ReactDOM.render( + + + , + document.getElementById('root'), +) diff --git a/packages/playground/src/shared/instances/scene.glb b/packages/playground/src/shared/instances/scene.glb new file mode 100644 index 0000000..ecd74ee Binary files /dev/null and b/packages/playground/src/shared/instances/scene.glb differ diff --git a/packages/playground/src/shared/r3f-rocket/App.tsx b/packages/playground/src/shared/r3f-rocket/App.tsx index da23f66..19feef1 100644 --- a/packages/playground/src/shared/r3f-rocket/App.tsx +++ b/packages/playground/src/shared/r3f-rocket/App.tsx @@ -1,11 +1,10 @@ import {editable as e, RefreshSnapshot, SheetProvider} from '@theatre/r3f' import {Stars} from '@react-three/drei' import {getProject} from '@theatre/core' -import React, {Suspense, useMemo, useState} from 'react' +import React, {Suspense, useState} from 'react' import {Canvas} from '@react-three/fiber' import {useGLTF, PerspectiveCamera} from '@react-three/drei' import sceneGLB from './scene.glb' -import {Color} from 'three' document.body.style.backgroundColor = '#171717' @@ -55,14 +54,8 @@ function App() { }} > - getProject('Space').sheet('Scene')}> - new Color(bg), [bg])} - near={16} - far={30} - uniqueName="Fog" - /> + + (undefined) +const ctx = createContext<{sheet: ISheet}>(undefined!) -const useWrapperContext = (): {sheet: ISheet | undefined} => { +const useWrapperContext = (): {sheet: ISheet} => { const val = useContext(ctx) if (!val) { throw new Error( @@ -25,23 +25,21 @@ export const useCurrentSheet = (): ISheet | undefined => { } const SheetProvider: React.FC<{ - getSheet: () => ISheet -}> = (props) => { + sheet: ISheet +}> = ({sheet, children}) => { const {scene, gl} = useThree((s) => ({scene: s.scene, gl: s.gl})) - const [sheet, setSheet] = useState(undefined) + + useEffect(() => { + if (!sheet || sheet.type !== 'Theatre_Sheet_PublicAPI') { + throw new Error(`sheet in has an invalid value`) + } + }, [sheet]) useLayoutEffect(() => { - const sheet = props.getSheet() - if (!sheet || sheet.type !== 'Theatre_Sheet_PublicAPI') { - throw new Error( - `getSheet() in has returned an invalid value`, - ) - } - setSheet(sheet) bindToCanvas({gl, scene}) }, [scene, gl]) - return {props.children} + return {children} } export default SheetProvider diff --git a/packages/r3f/src/components/EditableProxy.tsx b/packages/r3f/src/components/EditableProxy.tsx index 41576dd..2608298 100644 --- a/packages/r3f/src/components/EditableProxy.tsx +++ b/packages/r3f/src/components/EditableProxy.tsx @@ -15,14 +15,11 @@ import {invalidate, useFrame, useThree} from '@react-three/fiber' import {useDragDetector} from './DragDetector' export interface EditableProxyProps { - editableName: string + storeKey: string object: Object3D } -const EditableProxy: VFC = ({ - editableName: uniqueName, - object, -}) => { +const EditableProxy: VFC = ({storeKey, object}) => { const editorObject = getEditorSheetObject() const [setSnapshotProxyObject, editables] = useEditorStore( (state) => [state.setSnapshotProxyObject, state.editables], @@ -31,17 +28,17 @@ const EditableProxy: VFC = ({ const dragging = useDragDetector() - const editable = editables[uniqueName] + const editable = editables[storeKey] const selected = useSelected() const showOverlayIcons = useVal(editorObject?.props.viewport.showOverlayIcons) ?? false useEffect(() => { - setSnapshotProxyObject(object, uniqueName) + setSnapshotProxyObject(object, storeKey) - return () => setSnapshotProxyObject(null, uniqueName) - }, [uniqueName, object, setSnapshotProxyObject]) + return () => setSnapshotProxyObject(null, storeKey) + }, [storeKey, object, setSnapshotProxyObject]) useLayoutEffect(() => { const originalVisibility = object.visible @@ -68,7 +65,7 @@ const EditableProxy: VFC = ({ return } - if (selected === uniqueName || hovered) { + if (selected === storeKey || hovered) { scene.add(helper) invalidate() } @@ -93,6 +90,30 @@ const EditableProxy: VFC = ({ } }, [dragging]) + // subscribe to external changes + useEffect(() => { + const sheetObject = editable.sheetObject + const objectConfig = editable.objectConfig + + const setFromTheatre = (newValues: any) => { + // @ts-ignore + Object.entries(objectConfig.props).forEach(([key, value]) => { + // @ts-ignore + return value.apply(newValues[key], object) + }) + objectConfig.updateObject?.(object) + invalidate() + } + + setFromTheatre(sheetObject.value) + + const untap = sheetObject.onValuesChange(setFromTheatre) + + return () => { + untap() + } + }, [editable]) + return ( <> = ({ e.stopPropagation() const theatreObject = - useEditorStore.getState().editables[uniqueName].sheetObject + useEditorStore.getState().editables[storeKey].sheetObject if (!theatreObject) { - console.log('no theatre object for', uniqueName) + console.log('no theatre object for', storeKey) } else { studio.setSelection([theatreObject]) } @@ -129,8 +150,7 @@ const EditableProxy: VFC = ({ > {(showOverlayIcons || - (editable.objectConfig.dimensionless && - selected !== uniqueName)) && ( + (editable.objectConfig.dimensionless && selected !== storeKey)) && ( = ({ if (e.delta < 2) { e.stopPropagation() const theatreObject = - useEditorStore.getState().editables[uniqueName].sheetObject + useEditorStore.getState().editables[storeKey].sheetObject if (!theatreObject) { - console.log('no theatre object for', uniqueName) + console.log('no theatre object for', storeKey) } else { studio.setSelection([theatreObject]) } diff --git a/packages/r3f/src/components/ProxyManager.tsx b/packages/r3f/src/components/ProxyManager.tsx index 596e69e..3c01da5 100644 --- a/packages/r3f/src/components/ProxyManager.tsx +++ b/packages/r3f/src/components/ProxyManager.tsx @@ -1,11 +1,5 @@ import type {VFC} from 'react' -import React, { - useEffect, - useLayoutEffect, - useMemo, - useRef, - useState, -} from 'react' +import React, {useLayoutEffect, useMemo, useRef, useState} from 'react' import type {Editable} from '../store' import {useEditorStore} from '../store' import {createPortal} from '@react-three/fiber' @@ -15,11 +9,10 @@ import TransformControls from './TransformControls' import shallow from 'zustand/shallow' import type {Material, Mesh, Object3D} from 'three' import {MeshBasicMaterial, MeshPhongMaterial} from 'three' -import type {IScrub} from '@theatre/studio'; +import type {IScrub} from '@theatre/studio' import studio from '@theatre/studio' import {useSelected} from './useSelected' import {useVal} from '@theatre/react' -import useInvalidate from './useInvalidate' import {getEditorSheetObject} from './editorStuff' export interface ProxyManagerProps { @@ -55,8 +48,6 @@ const ProxyManager: VFC = ({orbitControlsRef}) => { } >({}) - const invalidate = useInvalidate() - // set up scene proxies useLayoutEffect(() => { if (!sceneProxy) { @@ -68,15 +59,15 @@ const ProxyManager: VFC = ({orbitControlsRef}) => { sceneProxy.traverse((object) => { if (object.userData.__editable) { // there are duplicate uniqueNames in the scene, only display one instance in the editor - if (editableProxies[object.userData.__editableName]) { + if (editableProxies[object.userData.__storeKey]) { object.parent!.remove(object) } else { - const uniqueName = object.userData.__editableName + const uniqueName = object.userData.__storeKey editableProxies[uniqueName] = { portal: createPortal( , object.parent!, @@ -95,32 +86,6 @@ const ProxyManager: VFC = ({orbitControlsRef}) => { const editableProxyOfSelected = selected && editableProxies[selected] const editable = selected ? editables[selected] : undefined - // subscribe to external changes - useEffect(() => { - if (!editableProxyOfSelected || !editable) return - const object = editableProxyOfSelected.object - const sheetObject = editableProxyOfSelected.editable.sheetObject - const objectConfig = editable.objectConfig - - const setFromTheatre = (newValues: any) => { - // @ts-ignore - Object.entries(objectConfig.props).forEach(([key, value]) => { - // @ts-ignore - return value.apply(newValues[key], object) - }) - objectConfig.updateObject?.(object) - invalidate() - } - - setFromTheatre(sheetObject.value) - - const untap = sheetObject.onValuesChange(setFromTheatre) - - return () => { - untap() - } - }, [editableProxyOfSelected, selected]) - // set up viewport shading modes const [renderMaterials, setRenderMaterials] = useState<{ [id: string]: Material | Material[] diff --git a/packages/r3f/src/components/editable.tsx b/packages/r3f/src/components/editable.tsx index 7c9e4e9..6c3f341 100644 --- a/packages/r3f/src/components/editable.tsx +++ b/packages/r3f/src/components/editable.tsx @@ -8,6 +8,7 @@ import useInvalidate from './useInvalidate' import {useCurrentSheet} from '../SheetProvider' import defaultEditableFactoryConfig from '../defaultEditableFactoryConfig' import type {EditableFactoryConfig} from '../editableFactoryConfigUtils' +import {makeStoreKey} from '../utils' const createEditable = ( config: EditableFactoryConfig, @@ -47,7 +48,9 @@ const createEditable = ( const objectRef = useRef() - const sheet = useCurrentSheet() + const sheet = useCurrentSheet()! + + const storeKey = makeStoreKey(sheet, uniqueName) const [sheetObject, setSheetObject] = useState< undefined | ISheetObject<$FixMe> @@ -75,14 +78,14 @@ const createEditable = ( if (objRef) objRef!.current = sheetObject - useEditorStore.getState().addEditable(uniqueName, { + useEditorStore.getState().addEditable(storeKey, { type: actualType, sheetObject, visibleOnlyInEditor: visible === 'editor', // @ts-ignore objectConfig: config[actualType], }) - }, [sheet, uniqueName]) + }, [sheet, storeKey]) // store initial values of props useLayoutEffect(() => { @@ -95,7 +98,6 @@ const createEditable = ( ), ) }, [ - uniqueName, sheetObject, // @ts-ignore ...Object.keys(config[actualType].props).map( @@ -138,7 +140,7 @@ const createEditable = ( visible={visible !== 'editor' && visible} userData={{ __editable: true, - __editableName: uniqueName, + __storeKey: storeKey, }} /> ) diff --git a/packages/r3f/src/components/useSelected.tsx b/packages/r3f/src/components/useSelected.tsx index fa13a97..e158a2c 100644 --- a/packages/r3f/src/components/useSelected.tsx +++ b/packages/r3f/src/components/useSelected.tsx @@ -3,6 +3,7 @@ import {allRegisteredObjects} from '../store' import studio from '@theatre/studio' import type {ISheetObject} from '@theatre/core' import type {$IntentionalAny} from '../types' +import {makeStoreKey} from '../utils' export function useSelected(): undefined | string { const [state, set] = useState(undefined) @@ -19,7 +20,7 @@ export function useSelected(): undefined | string { if (!item) { set(undefined) } else { - set(item.address.objectKey) + set(makeStoreKey(item.sheet, item.address.objectKey)) } } setFromStudio(studio.selection) @@ -38,6 +39,6 @@ export function getSelected(): undefined | string { if (!item) { return undefined } else { - return item.address.objectKey + return makeStoreKey(item.sheet, item.address.objectKey) } } diff --git a/packages/r3f/src/utils.ts b/packages/r3f/src/utils.ts index 619bd2d..85e2046 100644 --- a/packages/r3f/src/utils.ts +++ b/packages/r3f/src/utils.ts @@ -1,3 +1,7 @@ import {useEditorStore} from './store' +import type {ISheet} from '@theatre/core' export const refreshSnapshot = useEditorStore.getState().createSnapshot + +export const makeStoreKey = (sheet: ISheet, name: string) => + `${sheet.address.sheetId}:${sheet.address.sheetInstanceId}:${name}`