diff --git a/src/App.tsx b/src/App.tsx
index 5ac6095..08e976a 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -7,7 +7,6 @@ import LogoutPage from './components/pages/LogoutPage'
import RegisterPage from './components/pages/RegisterPage'
import Chart from './components/pages/Chart'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
-import './App.css'
import { observer } from 'mobx-react'
import { ThemeProvider, CssBaseline } from '@mui/material'
import appTheme from './Theme'
@@ -21,8 +20,9 @@ import '@aws-amplify/ui-react/styles.css';
import { useMediaQuery } from '@mui/material';
import { useMediaQuery as useResponsiveQuery } from 'react-responsive';
import screenfull from 'screenfull';
-import React, { useRef } from 'react';
+import React, { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
+import './App.css'
import awsconfig from './aws-exports';
Amplify.configure(awsconfig);
@@ -37,6 +37,7 @@ function App({ signOut, user }: WithAuthenticatorProps) {
const isPortrait = useDeviceOrientation();
const isSmallScreen = useMediaQuery('(max-width:600px)');
const elementRef = useRef(null);
+ const [ displayAppBar, setDisplayAppBar ] = useState('inherit');
const toggleFullscreen = () => {
if (screenfull.isEnabled) {
@@ -53,6 +54,19 @@ function App({ signOut, user }: WithAuthenticatorProps) {
alert(t('app.switch_landscape'));
}
}, [isSmallScreen, isPortrait, t]);
+
+ React.useEffect(() => {
+ // Parse URL parameters
+ const urlParams = new URLSearchParams(window.location.search);
+ const cssParam = urlParams.get('css'); // Assuming 'css' is the parameter name
+
+ // Set gui mode if parameter is present.
+ if (cssParam === 'gui') {
+ viewerState.setIsGuiMode(true)
+ setDisplayAppBar('none')
+ }
+ }, []);
+
// On file system we'll have a folder per model containing cached/versioned gltf, possibly .osim file, data files, display
// preferences
// urls could be something like:
@@ -71,7 +85,9 @@ function App({ signOut, user }: WithAuthenticatorProps) {
-
+
+
+
} />
@@ -84,10 +100,6 @@ function App({ signOut, user }: WithAuthenticatorProps) {
path="/viewer/:urlParam?"
element={}
/>
- }
- />
}
diff --git a/src/backend/README_1.md b/src/backend/README_1.md
deleted file mode 100644
index b2e4949..0000000
--- a/src/backend/README_1.md
+++ /dev/null
@@ -1,59 +0,0 @@
-*Requisites:** Conda and python installed.
-
-1. Create environment using the `environment.yml` file:
-
- `conda env create -f environment.yml`
-
-2. Activate environment:
-
- `conda activate opensim-viewer-bend`
-
-3. Start server:
-
- `python manage.py runserver`
-
-### Instructions for database migration
-
- 1. Create migration files:
-
- `python manage.py makemigrations`
-
- 2. Migrate the database (warning: data can be lost)
-
- `python manage.py migrate`
-
-### Instructions for recreating ERD diagram
-
-Instructions in this [Link](https://www.wplogout.com/export-database-diagrams-erd-from-django/).
-
-### Instructions for localization
-
-Instructions in this [Link](https://docs.djangoproject.com/en/4.2/topics/i18n/translation/).
-
-Inside of backend app folder:
-
-1. Create files for a language:
-
- `django-admin makemessages -l `
-
-2. Compile messages:
-
- `django-admin compilemessages`
-
-### Instruction for testing
-
-- Execute all tests:
-
- `python manage.py test --verbosity=0`
-
-
-General
--------
-- This folder contains scripts to convert OpenSim based data files into gltf format
-- We use third party library pygltflib to manipulate the gltf structure thus avoiding low level json file manipulation and encoding/decoding whenever possible.
-
-Description of specific python files:
--------------------------------------
-- openSimData2Gltf.py: gneric utilities that take columns of data and time stamps and create the corresponding Accessors in the passed in GLTF2 structure
-- convert{xxx}2Gltf.py: Utilities for converting files with extension {xxx} to gltf, the convention is to produce a file with the same name but with different extension, unless an output file is specified
-
\ No newline at end of file
diff --git a/src/backend/lambda_function.py b/src/backend/lambda_function.py
index 6f129eb..1109f44 100644
--- a/src/backend/lambda_function.py
+++ b/src/backend/lambda_function.py
@@ -48,6 +48,12 @@ def handler(event, context):
print("Attempting to download")
s3.download_file(source_bucket, object_key, file_name)
+ user_uuid = ""
+ if "user_uuid" in event:
+ user_uuid = event["user_uuid"] + "/"
+
+ print("user_uuid: " + user_uuid)
+
print("setup conversion function")
target_bucket = 'opensim-viewer-public-download'
print("file_name", file_name)
@@ -62,7 +68,8 @@ def handler(event, context):
gltfJson.save(destinationFile)
print("Gltf file saved")
destinationFileName = Path(file_name).with_suffix('.gltf')
- strDestinationFileName = str(destinationFileName).split('/')[-1]
+ strDestinationFileName = user_uuid + str(destinationFileName).split('/')[-1]
+ print("Destination File Name: " + strDestinationFileName)
# print("DestinationFile string", strDestinationFileName)
s3.upload_file(destinationFile, target_bucket, strDestinationFileName)
# print("File upload launched")
diff --git a/src/backend/readme.md b/src/backend/readme.md
deleted file mode 100644
index b2e4949..0000000
--- a/src/backend/readme.md
+++ /dev/null
@@ -1,59 +0,0 @@
-*Requisites:** Conda and python installed.
-
-1. Create environment using the `environment.yml` file:
-
- `conda env create -f environment.yml`
-
-2. Activate environment:
-
- `conda activate opensim-viewer-bend`
-
-3. Start server:
-
- `python manage.py runserver`
-
-### Instructions for database migration
-
- 1. Create migration files:
-
- `python manage.py makemigrations`
-
- 2. Migrate the database (warning: data can be lost)
-
- `python manage.py migrate`
-
-### Instructions for recreating ERD diagram
-
-Instructions in this [Link](https://www.wplogout.com/export-database-diagrams-erd-from-django/).
-
-### Instructions for localization
-
-Instructions in this [Link](https://docs.djangoproject.com/en/4.2/topics/i18n/translation/).
-
-Inside of backend app folder:
-
-1. Create files for a language:
-
- `django-admin makemessages -l `
-
-2. Compile messages:
-
- `django-admin compilemessages`
-
-### Instruction for testing
-
-- Execute all tests:
-
- `python manage.py test --verbosity=0`
-
-
-General
--------
-- This folder contains scripts to convert OpenSim based data files into gltf format
-- We use third party library pygltflib to manipulate the gltf structure thus avoiding low level json file manipulation and encoding/decoding whenever possible.
-
-Description of specific python files:
--------------------------------------
-- openSimData2Gltf.py: gneric utilities that take columns of data and time stamps and create the corresponding Accessors in the passed in GLTF2 structure
-- convert{xxx}2Gltf.py: Utilities for converting files with extension {xxx} to gltf, the convention is to produce a file with the same name but with different extension, unless an output file is specified
-
\ No newline at end of file
diff --git a/src/components/Components/DropFile.tsx b/src/components/Components/DropFile.tsx
index 69aa155..9c4300d 100644
--- a/src/components/Components/DropFile.tsx
+++ b/src/components/Components/DropFile.tsx
@@ -20,7 +20,11 @@ const lambda = new AWS.Lambda({
region: 'us-west-2', // replace with your region
});
-const FileDropArea = observer(() => {
+interface FileDropAreaProps {
+ paddingY?: number;
+}
+
+const FileDropArea: React.FC =observer(({ paddingY = 16}) => {
const { t } = useTranslation();
const navigate = useNavigate();
const location = useLocation();
@@ -108,16 +112,15 @@ const FileDropArea = observer(() => {
viewerState.isLocalUpload = true
} else {
Storage.put(file.name, file).then(()=>{
- /*
- const api_url = 'https://eudfxg3a9l.execute-api.us-west-2.amazonaws.com/dev/'
- axios.post(api_url, data).then(response => {
- const gltf_url = response.data['url']; .replace(/\.\w+$/, '.gltf')
- appState.setCurrentModelPath(gltf_url); */
+
+ let user_uuid = viewerState.user_uuid;
+
const params: AWS.Lambda.InvocationRequest = {
FunctionName: 'opensim-viewer-func', // replace with your Lambda function's name
Payload: JSON.stringify({
s3: 'opensimviewer-input-bucket101047-dev',
- key: 'public/'+file.name
+ key: 'public/' + file.name,
+ user_uuid: user_uuid
})
};
lambda.invoke(params, (err: any, data: any) => {
@@ -125,7 +128,7 @@ const FileDropArea = observer(() => {
console.error(err);
} else {
const key = file.name.replace(/\.\w+$/, '.gltf')
- const gltf_url = "https://s3.us-west-2.amazonaws.com/opensim-viewer-public-download/"+key
+ const gltf_url = "https://s3.us-west-2.amazonaws.com/opensim-viewer-public-download/" + user_uuid + "/"+key
/* appState.setCurrentModelPath(gltf_url); */
navigate("/viewer/"+encodeURIComponent(gltf_url))
console.log('Lambda function invoked successfully:', data);
@@ -152,7 +155,7 @@ const FileDropArea = observer(() => {
sx={{
border: '1px dashed gray',
borderRadius: '4px',
- padding: '16px',
+ padding: `${paddingY}px`,
textAlign: 'center',
cursor: 'pointer',
}}
diff --git a/src/components/Components/FloatingControlsPanel.css b/src/components/Components/FloatingControlsPanel.css
index 0588b63..c59e2ae 100644
--- a/src/components/Components/FloatingControlsPanel.css
+++ b/src/components/Components/FloatingControlsPanel.css
@@ -1,7 +1,6 @@
/* FloatingButton.css */
.floating-buttons-container {
position: absolute;
- top: 80px;
right: 25px;
z-index: 999;
}
diff --git a/src/components/Components/FloatingControlsPanel.tsx b/src/components/Components/FloatingControlsPanel.tsx
index 70ebce6..6df288f 100644
--- a/src/components/Components/FloatingControlsPanel.tsx
+++ b/src/components/Components/FloatingControlsPanel.tsx
@@ -16,6 +16,7 @@ import { ModelInfo } from '../../state/ModelUIState';
interface FloatingControlsPanelProps {
videoRecorderRef: any;
info: ModelInfo;
+ top: string;
}
function FloatingControlsPanel(props :FloatingControlsPanelProps) {
@@ -29,7 +30,7 @@ function FloatingControlsPanel(props :FloatingControlsPanelProps) {
};
return (
-
+
diff --git a/src/components/pages/BottomBar.tsx b/src/components/pages/BottomBar.tsx
index 78ffc73..269d32c 100644
--- a/src/components/pages/BottomBar.tsx
+++ b/src/components/pages/BottomBar.tsx
@@ -10,6 +10,7 @@ import { observer } from 'mobx-react'
import { AnimationClip } from 'three';
import { useTranslation } from 'react-i18next';
import { useModelContext } from '../../state/ModelUIStateContext';
+import { Camera } from 'three/src/cameras/Camera'
import React, { useCallback, useRef } from 'react';
const NonAnimatedSlider = styled(Slider)(({ theme } : {theme:any}) => ({
@@ -39,6 +40,7 @@ const BottomBar = React.forwardRef(function CustomContent(
const [speed, setSpeed] = useState(1.0);
const [play, setPlay] = useState(false);
const [selectedAnim, setSelectedAnim] = useState("");
+ const [selectedCam, setSelectedCam] = useState("");
const isExtraSmallScreen = useMediaQuery((theme:any) => theme.breakpoints.only('xs'));
const isSmallScreen = useMediaQuery((theme:any) => theme.breakpoints.only('sm'));
@@ -47,6 +49,7 @@ const BottomBar = React.forwardRef(function CustomContent(
const minWidthSlider = isExtraSmallScreen ? 150 : isSmallScreen ? 175 : isMediumScreen ? 250 : 300; // Adjust values as needed
const maxWidthTime = 45;
+
const handleAnimationChange = useCallback((animationName: string, animate: boolean) => {
const targetName = animationName
setSelectedAnim(animationName);
@@ -63,11 +66,29 @@ const BottomBar = React.forwardRef(function CustomContent(
//setAge(event.target.value as string);
}, [curState]);
+ const handleCameraChange = useCallback((cameraName: string) => {
+ const targetName = cameraName
+ setSelectedCam(cameraName);
+
+ const idx = curState.cameras.findIndex((value: Camera, index: number)=>{return (value.name === targetName)})
+ if (idx !== -1) {
+ curState.setCurrentCameraIndex(idx)
+ }
+
+ curState.setCurrentFrame(0);
+ //setAge(event.target.value as string);
+ }, [curState]);
+
const handleAnimationChangeEvent = (event: SelectChangeEvent) => {
const targetName = event.target.value as string
handleAnimationChange(targetName, true)
};
+ const handleCameraChangeEvent = (event: SelectChangeEvent) => {
+ const targetName = event.target.value as string
+ handleCameraChange(targetName)
+ };
+
function togglePlayAnimation() {
curState.setAnimating(!curState.animating);
setPlay(!play);
@@ -101,11 +122,19 @@ const BottomBar = React.forwardRef(function CustomContent(
}
}, [curState.animations, handleAnimationChange]);
+ useEffect(() => {
+ if (curState.cameras.length > 0) {
+ setSelectedCam(curState.cameras[0].name)
+ handleCameraChange(curState.cameras[0].name)
+ }
+ }, [curState.cameras, handleCameraChange]);
+
return (
+ { curState.animations.length < 1 ? null : (
+ )}
+
+ { curState.cameras.length < 1 ? null : (
+
+
+
+
+
+ )}
diff --git a/src/components/pages/HomePage.tsx b/src/components/pages/HomePage.tsx
index 6b92d1e..62168c4 100644
--- a/src/components/pages/HomePage.tsx
+++ b/src/components/pages/HomePage.tsx
@@ -8,7 +8,7 @@ const HomePage = () => {
{t('welcome_title')}
-
+
)
diff --git a/src/components/pages/ModelViewPage.tsx b/src/components/pages/ModelViewPage.tsx
index e04e7cc..3b885af 100644
--- a/src/components/pages/ModelViewPage.tsx
+++ b/src/components/pages/ModelViewPage.tsx
@@ -53,39 +53,62 @@ interface ViewerProps {
export function ModelViewPage({url, embedded, noFloor}:ViewerProps) {
const bottomBarRef = useRef(null);
+ const videoRecorderRef = useRef(null);
+
+
+ // TODO: Move to a general styles file?
+ const leftMenuWidth = 60;
+ const drawerContentWidth = 250;
+
+ const [heightBottomBar, setHeightBottomBar] = useState(0);
const theme = useTheme();
const curState = useModelContext();
let { urlParam } = useParams();
- const [heightBottomBar, setHeightBottomBar] = useState(0);
+ const [uiState] = React.useState(curState);
+ const [menuOpen, setMenuOpen] = React.useState(false);
+ const [selectedTabName, setSelectedTabName] = React.useState("File");
+
+ const [ displaySideBar, setDisplaySideBar ] = useState('inherit');
+ const [canvasWidth, setCanvasWidth] = useState("calc(100vw - " + (leftMenuWidth + (menuOpen ? drawerContentWidth : 0)) + "px)");
+ const [canvasHeight, setCanvasHeight] = useState("calc(100vh - 68px - " + heightBottomBar + "px)");
+ const [canvasLeft, setCanvasLeft] = useState(leftMenuWidth + (menuOpen ? drawerContentWidth : 0));
+ const [floatingButtonsContainerTop, setFloatingButtonsContainerTop] = useState("80px");
useEffect(() => {
if (bottomBarRef.current) {
const heightBottomBar = bottomBarRef.current.offsetHeight;
setHeightBottomBar(bottomBarRef.current.offsetHeight);
+ setCanvasHeight("calc(100vh - 68px - " + heightBottomBar + "px)");
+
// Do something with heightBottomBar if needed
console.log('Height of BottomBar:', heightBottomBar);
}
}, []);
+ React.useEffect(() => {
+ // Change interface if we are in GUI mode.
+ if (viewerState.isGuiMode) {
+ setDisplaySideBar('none');
+ setCanvasWidth('100%');
+ setCanvasHeight('calc(100vh - 68px)');
+ setCanvasLeft(0);
+ setFloatingButtonsContainerTop("12px")
+ }
+ }, []);
+
//console.log(urlParam);
if (urlParam!== undefined) {
var decodedUrl = decodeURIComponent(urlParam);
viewerState.setCurrentModelPath(decodedUrl);
curState.setCurrentModelPath(viewerState.currentModelPath);
// If urlParam is not undefined, this means it is getting the model from S3 and not from local.
- viewerState.isLocalUpload = false;
+ viewerState.setIsLocalUpload(false);
}
else
curState.setCurrentModelPath(viewerState.currentModelPath);
- const [uiState] = React.useState(curState);
- const [menuOpen, setMenuOpen] = React.useState(false);
- const [selectedTabName, setSelectedTabName] = React.useState("File");
-
- const videoRecorderRef = useRef(null);
-
function toggleOpenMenu(name: string = "") {
// If same name, or empty just toggle.
if (name === selectedTabName || name === "") setMenuOpen(!menuOpen);
@@ -94,39 +117,35 @@ export function ModelViewPage({url, embedded, noFloor}:ViewerProps) {
// Always store same name.
setSelectedTabName(name);
}
-
- // TODO: Move to a general styles file?
- const leftMenuWidth = 60;
- const drawerContentWidth = 250;
-
return (
-
+
+ info={new ModelInfo(uiState.modelInfo.model_name, uiState.modelInfo.desc, uiState.modelInfo.authors)}
+ top={floatingButtonsContainerTop}/>