First
This commit is contained in:
commit
98e799b7df
21 changed files with 1936 additions and 0 deletions
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
node_modules
|
||||||
|
.env
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
21
.vscode/launch.json
vendored
Normal file
21
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
{
|
||||||
|
// Use IntelliSense to learn about possible attributes.
|
||||||
|
// Hover to view descriptions of existing attributes.
|
||||||
|
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||||
|
"version": "0.2.0",
|
||||||
|
"configurations": [
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "node",
|
||||||
|
"request": "launch",
|
||||||
|
"name": "Launch Server",
|
||||||
|
"runtimeExecutable": "npm",
|
||||||
|
"runtimeArgs": [
|
||||||
|
"start"
|
||||||
|
],
|
||||||
|
"skipFiles": [
|
||||||
|
"<node_internals>/**/*.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
20
Dockerfile
Normal file
20
Dockerfile
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
FROM node:22.9
|
||||||
|
|
||||||
|
# We have to install nodemon globally before moving into the working directory
|
||||||
|
RUN npm install -g nodemon
|
||||||
|
|
||||||
|
# Create app directory
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Install app dependencies
|
||||||
|
# A wildcard is used to ensure both package.json AND package-lock.json are copied
|
||||||
|
COPY package*.json ./
|
||||||
|
|
||||||
|
RUN npm install
|
||||||
|
|
||||||
|
# Bundle app source
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
CMD ./start.sh
|
21
LICENSE
Normal file
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2024 Autodesk
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
198
README.md
Normal file
198
README.md
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
<!--
|
||||||
|
*** DanieleViero
|
||||||
|
*** §§§§§§§§§§§§§§§§§§§§§§§§§§§§_-Main readme template-_§§§§§§§§§§§§§§§§§§§§§§§§§§
|
||||||
|
*** Make sure to use this as initiate on all projects. Must be used along with the
|
||||||
|
*** .gitignore provided as a template
|
||||||
|
*** contact ref
|
||||||
|
*** user@domain.com
|
||||||
|
*** user@domain.com
|
||||||
|
*** user@domain.com
|
||||||
|
*** user@domain.com
|
||||||
|
-->
|
||||||
|
|
||||||
|
<!--
|
||||||
|
*** using ref style capability
|
||||||
|
*** for more info: https://www.markdownguide.org/basic-syntax/#reference-style-links
|
||||||
|
*** Reference links are enclosed in brackets [ ] instead of parentheses ( ).
|
||||||
|
*** bottom of document for the declaration of the reference variables
|
||||||
|
***
|
||||||
|
-->
|
||||||
|
<!-- Header quick ref -->
|
||||||
|
![Development][Development]
|
||||||
|
|
||||||
|
<!-- COMPANY LOGO -->
|
||||||
|
<br />
|
||||||
|
<div align="center">
|
||||||
|
<a href="null">
|
||||||
|
<img src="img/gruner.png" alt="Logo" width="260" height="170">
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<h3 align="center">Website viewer APS</h3>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
------ Website viewer Autodesk Platform Services driven -----
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- TABLE OF CONTENTS -->
|
||||||
|
<!-- REMOVE IF NOT RELEVANT -->
|
||||||
|
<details>
|
||||||
|
<summary>Table of Contents</summary>
|
||||||
|
<ol>
|
||||||
|
<li>
|
||||||
|
<a href="#project-description">Project Description</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="#getting-started">Getting Started</a>
|
||||||
|
</li>
|
||||||
|
<li><a href="#contributing">Contributing</a></li>
|
||||||
|
<li><a href="#contact">Contact</a></li>
|
||||||
|
</ol>
|
||||||
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- ABOUT THE PROJECT -->
|
||||||
|
## Project Description
|
||||||
|
|
||||||
|
APS model viewer implementation for marketing purposes.
|
||||||
|
|
||||||
|
|
||||||
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||||
|
|
||||||
|
<!-- GETTING STARTED -->
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
To get the latest compiled version, go to: [Github Releases](https://github.com/grunergroup/gTT_APSwebViewer/releases)
|
||||||
|
|
||||||
|
|
||||||
|
### Docker Setup
|
||||||
|
- clone the Repo
|
||||||
|
- Create a _.env_ file in the project folder, and populae it with the snippet below,
|
||||||
|
replacing `<client-id>` and `<client-secret>` with your APS Client ID and Client Secret:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
APS_CLIENT_ID=<INSERT ID>
|
||||||
|
APS_CLIENT_SECRET=<INSERT SECRET>
|
||||||
|
APS_BUCKET=
|
||||||
|
presetModel=BPZ_00_GRU_SIT_STR_MOD_EXI_Powerhouse.rvt # Insert filename of the model
|
||||||
|
```
|
||||||
|
|
||||||
|
- edit `docker-compose.yml` and change the port.
|
||||||
|
|
||||||
|
|
||||||
|
```yml
|
||||||
|
ports:
|
||||||
|
- "127.0.0.1:808X:8080"
|
||||||
|
```
|
||||||
|
|
||||||
|
- edit the
|
||||||
|
- Run the application, `docker compose up -d` in terminal
|
||||||
|
- Open http://localhost:8080
|
||||||
|
|
||||||
|
|
||||||
|
### NGINX Setup
|
||||||
|
|
||||||
|
1. Edit the `sudo nano /etc/nginx/nginx.conf`.
|
||||||
|
|
||||||
|
|
||||||
|
```js
|
||||||
|
server {
|
||||||
|
listen 80;
|
||||||
|
server_name localhost;
|
||||||
|
|
||||||
|
#charset koi8-r;
|
||||||
|
|
||||||
|
#access_log /var/log/nginx/host.access.log main;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
proxy_pass http://127.0.0.1:8080;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~/test(.*)$ {
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $remote_addr;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_pass http://127.0.0.1:8081/$1;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
...
|
||||||
|
```
|
||||||
|
- copy the `location ~/test..`
|
||||||
|
- adjust the path to your liking
|
||||||
|
- adjust `proxy_pass http://127.0.0.1:808X/$1;`port to the port configured in your docker-compose.yml
|
||||||
|
|
||||||
|
2. reload nginx to activate the new configuration `sudo systemctl reload nginx.service`.
|
||||||
|
|
||||||
|
The viewer should now be displaying the Model on the configured URL.
|
||||||
|
|
||||||
|
### ASP Documentation
|
||||||
|
|
||||||
|
- [Link1](https://aps.autodesk.com/apis-and-services/viewer)
|
||||||
|
- [Link2](https://aps.autodesk.com/developer/overview/viewer-sdk)
|
||||||
|
- [Link3](https://aps.autodesk.com/en/docs/viewer/v7/developers_guide/overview/)
|
||||||
|
|
||||||
|
<!-- CONTRIBUTING -->
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
If you have a suggestion that would make this better, please fork the repo and create a pull request. You can also simply open an issue with the tag "enhancement".
|
||||||
|
Don't forget to give the project a star! Thanks again!
|
||||||
|
|
||||||
|
1. Fork the Project
|
||||||
|
2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`)
|
||||||
|
3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`)
|
||||||
|
4. Push to the Branch (`git push origin feature/AmazingFeature`)
|
||||||
|
5. Open a Pull Request
|
||||||
|
|
||||||
|
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||||
|
|
||||||
|
<!-- CONTACT -->
|
||||||
|
## Authors
|
||||||
|
|
||||||
|
Daniele Viero - daniele.viero@gruner.ch
|
||||||
|
|
||||||
|
David Friedrich - david.friedrich@gruner.ch
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<!-- MARKDOWN LINKS & IMAGES -->
|
||||||
|
<!-- https://www.markdownguide.org/basic-syntax/#reference-style-links -->
|
||||||
|
|
||||||
|
[DepICT]: https://img.shields.io/badge/DEP-ICT-brightgreen
|
||||||
|
[DepICT-url]: https://gruner.sharepoint.com/sites/gn-su/SitePages/BSC_ICT.aspx
|
||||||
|
[DepBIM]: https://img.shields.io/badge/DEP-BIM-orange
|
||||||
|
[DepBIIM]: https://img.shields.io/badge/DEP-BIIM-orange
|
||||||
|
[DepBIM-url]: https://gruner.sharepoint.com/sites/intranet/SitePages/en/Home.aspx
|
||||||
|
[DepINFOM]: https://img.shields.io/badge/DEP-INFOMANAGEMENT-yellowgreen
|
||||||
|
[DepINFOM-url]: https://gruner.sharepoint.com/sites/intranet/SitePages/en/Home.aspx
|
||||||
|
[DepDATAM]: https://img.shields.io/badge/DEP-DATAMANAGEMENT-blue
|
||||||
|
[DepDATAM-url]: https://gruner.sharepoint.com/sites/intranet/SitePages/en/Home.aspx
|
||||||
|
|
||||||
|
[Integration]: https://img.shields.io/badge/-Integration-blue
|
||||||
|
[Automation]: https://img.shields.io/badge/-Automation-brightgreen
|
||||||
|
[Development]: https://img.shields.io/badge/-Development-yellow
|
||||||
|
[Support]: https://img.shields.io/badge/-Support-red
|
||||||
|
|
||||||
|
[Token]: https://img.shields.io/badge/-TOKEN-orange
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [APS credentials](https://forge.autodesk.com/en/docs/oauth/v2/tutorials/create-app)
|
||||||
|
- [Node.js](https://nodejs.org) (Long Term Support version is recommended)
|
||||||
|
- Command-line terminal such as [PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/overview)
|
||||||
|
or [bash](https://en.wikipedia.org/wiki/Bash_(Unix_shell)) (should already be available on your system)
|
||||||
|
|
||||||
|
> We recommend using [Visual Studio Code](https://code.visualstudio.com) which, among other benefits,
|
||||||
|
> provides an [integrated terminal](https://code.visualstudio.com/docs/terminal/basics) as well.
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This sample is licensed under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
||||||
|
Please see the [LICENSE](LICENSE) file for more details.
|
17
config.js
Normal file
17
config.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
require('dotenv').config();
|
||||||
|
|
||||||
|
let { APS_CLIENT_ID, APS_CLIENT_SECRET, APS_BUCKET, PORT, presetModel } = process.env;
|
||||||
|
if (!APS_CLIENT_ID || !APS_CLIENT_SECRET) {
|
||||||
|
console.warn('Missing some of the environment variables.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
APS_BUCKET = APS_BUCKET || `${APS_CLIENT_ID.toLowerCase()}-basic-app`;
|
||||||
|
PORT = PORT || 8080;
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
APS_CLIENT_ID,
|
||||||
|
APS_CLIENT_SECRET,
|
||||||
|
APS_BUCKET,
|
||||||
|
PORT,
|
||||||
|
presetModel
|
||||||
|
};
|
11
docker-compose.yml
Normal file
11
docker-compose.yml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
services:
|
||||||
|
app:
|
||||||
|
build: .
|
||||||
|
volumes:
|
||||||
|
- .:/usr/src/app
|
||||||
|
container_name: docker-node-tut
|
||||||
|
restart: always
|
||||||
|
ports:
|
||||||
|
- 127.0.0.1:8080:8080
|
||||||
|
command: bash -c /usr/src/app/start.sh
|
||||||
|
env_file: .env
|
1177
package-lock.json
generated
Normal file
1177
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
24
package.json
Normal file
24
package.json
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "aps-simple-viewer-nodejs",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Autodesk Platform Services application built by following the Simple Viewer tutorial from https://aps.autodesk.com/tutorials.",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"autodesk-platform-services"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@aps_sdk/authentication": "0.1.0-beta.1",
|
||||||
|
"@aps_sdk/autodesk-sdkmanager": "0.0.7-beta.1",
|
||||||
|
"@aps_sdk/model-derivative": "0.1.0-beta.1",
|
||||||
|
"@aps_sdk/oss": "0.1.0-beta.1",
|
||||||
|
"dotenv": "^16.4.1",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"express-formidable": "^1.2.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/forge-viewer": "^7.48.0"
|
||||||
|
}
|
||||||
|
}
|
14
routes/auth.js
Normal file
14
routes/auth.js
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
const express = require('express');
|
||||||
|
const { getPublicToken } = require('../services/aps.js');
|
||||||
|
|
||||||
|
let router = express.Router();
|
||||||
|
|
||||||
|
router.get('/api/auth/token', async function (req, res, next) {
|
||||||
|
try {
|
||||||
|
res.json(await getPublicToken());
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
67
routes/models.js
Normal file
67
routes/models.js
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
const express = require('express');
|
||||||
|
const formidable = require('express-formidable');
|
||||||
|
const { listObjects, uploadObject, translateObject, getManifest, urnify } = require('../services/aps.js');
|
||||||
|
const { presetModel } = require('../config.js');
|
||||||
|
|
||||||
|
let router = express.Router();
|
||||||
|
|
||||||
|
router.get('/api/models', async function (req, res, next) {
|
||||||
|
try {
|
||||||
|
const objects = await listObjects();
|
||||||
|
res.json(objects.map(o => ({
|
||||||
|
name: o.objectKey,
|
||||||
|
urn: urnify(o.objectId)
|
||||||
|
})));
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/api/selectedmodel', async function (req, res, next){
|
||||||
|
res.json({ name:presetModel });
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
router.get('/api/models/:urn/status', async function (req, res, next) {
|
||||||
|
try {
|
||||||
|
const manifest = await getManifest(req.params.urn);
|
||||||
|
if (manifest) {
|
||||||
|
let messages = [];
|
||||||
|
if (manifest.derivatives) {
|
||||||
|
for (const derivative of manifest.derivatives) {
|
||||||
|
messages = messages.concat(derivative.messages || []);
|
||||||
|
if (derivative.children) {
|
||||||
|
for (const child of derivative.children) {
|
||||||
|
messages.concat(child.messages || []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.json({ status: manifest.status, progress: manifest.progress, messages });
|
||||||
|
} else {
|
||||||
|
res.json({ status: 'n/a' });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
router.post('/api/models', formidable({ maxFileSize: Infinity }), async function (req, res, next) {
|
||||||
|
const file = req.files['model-file'];
|
||||||
|
if (!file) {
|
||||||
|
res.status(400).send('The required field ("model-file") is missing.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const obj = await uploadObject(file.name, file.path);
|
||||||
|
await translateObject(urnify(obj.objectId), req.fields['model-zip-entrypoint']);
|
||||||
|
res.json({
|
||||||
|
name: obj.objectKey,
|
||||||
|
urn: urnify(obj.objectId)
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
next(err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
8
server.js
Normal file
8
server.js
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
const express = require('express');
|
||||||
|
const { PORT } = require('./config.js');
|
||||||
|
|
||||||
|
let app = express();
|
||||||
|
app.use(express.static('wwwroot'));
|
||||||
|
app.use(require('./routes/auth.js'));
|
||||||
|
app.use(require('./routes/models.js'));
|
||||||
|
app.listen(PORT, function () { console.log(`Server listening on port ${PORT}...`); });
|
100
services/aps.js
Normal file
100
services/aps.js
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
const { SdkManagerBuilder } = require('@aps_sdk/autodesk-sdkmanager');
|
||||||
|
const { AuthenticationClient, Scopes } = require('@aps_sdk/authentication');
|
||||||
|
const { OssClient, CreateBucketsPayloadPolicyKeyEnum, CreateBucketXAdsRegionEnum } = require('@aps_sdk/oss');
|
||||||
|
const { ModelDerivativeClient, View, Type } = require('@aps_sdk/model-derivative');
|
||||||
|
const { APS_CLIENT_ID, APS_CLIENT_SECRET, APS_BUCKET } = require('../config.js');
|
||||||
|
|
||||||
|
const sdk = SdkManagerBuilder.create().build();
|
||||||
|
const authenticationClient = new AuthenticationClient(sdk);
|
||||||
|
const ossClient = new OssClient(sdk);
|
||||||
|
const modelDerivativeClient = new ModelDerivativeClient(sdk);
|
||||||
|
|
||||||
|
const service = module.exports = {};
|
||||||
|
|
||||||
|
service.getInternalToken = async () => {
|
||||||
|
const credentials = await authenticationClient.getTwoLeggedToken(APS_CLIENT_ID, APS_CLIENT_SECRET, [
|
||||||
|
Scopes.DataRead,
|
||||||
|
Scopes.DataCreate,
|
||||||
|
Scopes.DataWrite,
|
||||||
|
Scopes.BucketCreate,
|
||||||
|
Scopes.BucketRead
|
||||||
|
]);
|
||||||
|
return credentials;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.getPublicToken = async () => {
|
||||||
|
const credentials = await authenticationClient.getTwoLeggedToken(APS_CLIENT_ID, APS_CLIENT_SECRET, [
|
||||||
|
Scopes.DataRead
|
||||||
|
]);
|
||||||
|
return credentials;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.ensureBucketExists = async (bucketKey) => {
|
||||||
|
const { access_token } = await service.getInternalToken();
|
||||||
|
try {
|
||||||
|
await ossClient.getBucketDetails(access_token, bucketKey);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.axiosError.response.status === 404) {
|
||||||
|
await ossClient.createBucket(access_token, CreateBucketXAdsRegionEnum.Us, {
|
||||||
|
bucketKey: bucketKey,
|
||||||
|
policyKey: CreateBucketsPayloadPolicyKeyEnum.Persistent
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
service.listObjects = async () => {
|
||||||
|
await service.ensureBucketExists(APS_BUCKET);
|
||||||
|
const { access_token } = await service.getInternalToken();
|
||||||
|
let resp = await ossClient.getObjects(access_token, APS_BUCKET, { limit: 64 });
|
||||||
|
let objects = resp.items;
|
||||||
|
while (resp.next) {
|
||||||
|
const startAt = new URL(resp.next).searchParams.get('startAt');
|
||||||
|
resp = await ossClient.getObjects(access_token, APS_BUCKET, { limit: 64, startAt });
|
||||||
|
objects = objects.concat(resp.items);
|
||||||
|
}
|
||||||
|
return objects;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.uploadObject = async (objectName, filePath) => {
|
||||||
|
await service.ensureBucketExists(APS_BUCKET);
|
||||||
|
const { access_token } = await service.getInternalToken();
|
||||||
|
const obj = await ossClient.upload(APS_BUCKET, objectName, filePath, access_token);
|
||||||
|
return obj;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.translateObject = async (urn, rootFilename) => {
|
||||||
|
const { access_token } = await service.getInternalToken();
|
||||||
|
const job = await modelDerivativeClient.startJob(access_token, {
|
||||||
|
input: {
|
||||||
|
urn,
|
||||||
|
compressedUrn: !!rootFilename,
|
||||||
|
rootFilename
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
formats: [{
|
||||||
|
views: [View._2d, View._3d],
|
||||||
|
type: Type.Svf
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return job.result;
|
||||||
|
};
|
||||||
|
|
||||||
|
service.getManifest = async (urn) => {
|
||||||
|
const { access_token } = await service.getInternalToken();
|
||||||
|
try {
|
||||||
|
const manifest = await modelDerivativeClient.getManifest(access_token, urn);
|
||||||
|
return manifest;
|
||||||
|
} catch (err) {
|
||||||
|
if (err.axiosError.response.status === 404) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
service.urnify = (id) => Buffer.from(id).toString('base64').replace(/=/g, '');
|
1
start.sh
Normal file
1
start.sh
Normal file
|
@ -0,0 +1 @@
|
||||||
|
npm start run
|
BIN
thumbnail.png
Normal file
BIN
thumbnail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 502 KiB |
BIN
wwwroot/GruLogo_Inverted.png
Normal file
BIN
wwwroot/GruLogo_Inverted.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 35 KiB |
BIN
wwwroot/gruner.png
Normal file
BIN
wwwroot/gruner.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 71 KiB |
28
wwwroot/index.html
Normal file
28
wwwroot/index.html
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||||
|
<link rel="icon" type="image/x-icon" href="https://cdn.autodesk.io/favicon.ico">
|
||||||
|
<link rel="stylesheet" href="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/style.css">
|
||||||
|
<link rel="stylesheet" href="./main.css">
|
||||||
|
<title>Autodesk Platform Services: Simple Viewer</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="header">
|
||||||
|
<img class="logo" src="./gruner.png" >
|
||||||
|
<span class="title">Simple Viewer</span>
|
||||||
|
|
||||||
|
<input style="display: none" type="file" id="input">
|
||||||
|
</div>
|
||||||
|
<div id="preview"></div>
|
||||||
|
<div id="overlay"></div>
|
||||||
|
<script src="https://developer.api.autodesk.com/modelderivative/v2/viewers/7.*/viewer3D.js"></script>
|
||||||
|
<script>modelURN = '<%=modelURN%>'</script>
|
||||||
|
<script src="./main.js" type="module"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
55
wwwroot/main.css
Normal file
55
wwwroot/main.css
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
body, html {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
height: 100vh;
|
||||||
|
font-family: ArtifaktElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header, #preview, #overlay {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header {
|
||||||
|
height: 3em;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#preview, #overlay {
|
||||||
|
top: 3em;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#overlay {
|
||||||
|
z-index: 1;
|
||||||
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
|
padding: 1em;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#overlay > .notification {
|
||||||
|
margin: auto;
|
||||||
|
padding: 1em;
|
||||||
|
max-width: 50%;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header > * {
|
||||||
|
height: 2em;
|
||||||
|
margin: 0 0.5em;
|
||||||
|
font-size: 1em;
|
||||||
|
font-family: ArtifaktElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
#header .title {
|
||||||
|
flex: 1 0 auto;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#models {
|
||||||
|
flex: 0 1 auto;
|
||||||
|
min-width: 2em;
|
||||||
|
}
|
110
wwwroot/main.js
Normal file
110
wwwroot/main.js
Normal file
|
@ -0,0 +1,110 @@
|
||||||
|
import { initViewer, loadModel } from "./viewer.js";
|
||||||
|
|
||||||
|
// Declare the index number of the model in the array
|
||||||
|
const urnModelID = window.modelURN;
|
||||||
|
console.log(urnModelID)
|
||||||
|
|
||||||
|
|
||||||
|
// Function to change the title text
|
||||||
|
function changeTitle(newTitle) {
|
||||||
|
const titleElement = document.querySelector(".title");
|
||||||
|
if (titleElement) {
|
||||||
|
titleElement.textContent = newTitle;
|
||||||
|
} else {
|
||||||
|
console.error("Title element not found");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getModelsUrn() {
|
||||||
|
try {
|
||||||
|
// const resp = await fetch("/api/theurn");
|
||||||
|
// if (!resp.ok) {
|
||||||
|
// throw new Error(await resp.text());
|
||||||
|
// }
|
||||||
|
// const data = await resp.json();
|
||||||
|
// const urn = data.urn;
|
||||||
|
const resp1 = await fetch("/api/models");
|
||||||
|
const allmodels = await resp1.json();
|
||||||
|
const resp2 = await fetch("/api/selectedmodel")
|
||||||
|
const selectModelName = await resp2.json();
|
||||||
|
// const data2 = await resp2.json();
|
||||||
|
for (let i = 0; i < allmodels.length; i++){
|
||||||
|
if (allmodels[i].name == selectModelName.name){
|
||||||
|
console.log(allmodels[i].urn);
|
||||||
|
changeTitle(allmodels[i].name)
|
||||||
|
return allmodels[i].urn;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// const urns = models.map((model) => model.urn);
|
||||||
|
// const titles = models.map((model) => model.name);
|
||||||
|
// const randomIndex = Math.floor(Math.random() * urns.length);
|
||||||
|
// console.log(urns)
|
||||||
|
// changeTitle(titles[urnModelID]);
|
||||||
|
// return urn;
|
||||||
|
} catch (err) {
|
||||||
|
alert("Could not list models. See the console for more details.");
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
initViewer(document.getElementById("preview")).then((viewer) => {
|
||||||
|
loadModelwithViewer(viewer);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
async function loadModelwithViewer(viewer) {
|
||||||
|
if (window.onModelSelectedTimeout) {
|
||||||
|
clearTimeout(window.onModelSelectedTimeout);
|
||||||
|
delete window.onModelSelectedTimeout;
|
||||||
|
}
|
||||||
|
const urn = await getModelsUrn();
|
||||||
|
window.location.hash = urn;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const resp = await fetch(`/api/models/${urn}/status`);
|
||||||
|
if (!resp.ok) {
|
||||||
|
throw new Error(await resp.text());
|
||||||
|
}
|
||||||
|
const status = await resp.json();
|
||||||
|
switch (status.status) {
|
||||||
|
case "n/a":
|
||||||
|
showNotification(`Model has not been translated.`);
|
||||||
|
break;
|
||||||
|
case "inprogress":
|
||||||
|
showNotification(`Model is being translated (${status.progress})...`);
|
||||||
|
window.onModelSelectedTimeout = setTimeout(
|
||||||
|
onModelSelected,
|
||||||
|
5000,
|
||||||
|
viewer,
|
||||||
|
urn
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "failed":
|
||||||
|
showNotification(
|
||||||
|
`Translation failed. <ul>${status.messages
|
||||||
|
.map((msg) => `<li>${JSON.stringify(msg)}</li>`)
|
||||||
|
.join("")}</ul>`
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
clearNotification();
|
||||||
|
loadModel(viewer, urn);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
alert("Could not load model. See the console for more details.");
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showNotification(message) {
|
||||||
|
const overlay = document.getElementById("overlay");
|
||||||
|
overlay.innerHTML = `<div class="notification">${message}</div>`;
|
||||||
|
overlay.style.display = "flex";
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearNotification() {
|
||||||
|
const overlay = document.getElementById("overlay");
|
||||||
|
overlay.innerHTML = "";
|
||||||
|
overlay.style.display = "none";
|
||||||
|
}
|
59
wwwroot/viewer.js
Normal file
59
wwwroot/viewer.js
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
async function getAccessToken(callback) {
|
||||||
|
try {
|
||||||
|
const resp = await fetch("/api/auth/token");
|
||||||
|
if (!resp.ok) {
|
||||||
|
throw new Error(await resp.text());
|
||||||
|
}
|
||||||
|
const { access_token, expires_in } = await resp.json();
|
||||||
|
callback(access_token, expires_in);
|
||||||
|
} catch (err) {
|
||||||
|
alert("Could not obtain access token. See the console for more details.");
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function initViewer(container) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
Autodesk.Viewing.Initializer(
|
||||||
|
{ env: "AutodeskProduction", getAccessToken },
|
||||||
|
function () {
|
||||||
|
const config = {
|
||||||
|
extensions: ["Autodesk.DocumentBrowser"],
|
||||||
|
};
|
||||||
|
const viewer = new Autodesk.Viewing.GuiViewer3D(container, config);
|
||||||
|
viewer.start();
|
||||||
|
viewer.setTheme("light-theme");
|
||||||
|
resolve(viewer);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function loadModel(viewer, urn) {
|
||||||
|
return new Promise(function (resolve, reject) {
|
||||||
|
function onDocumentLoadSuccess(doc) {
|
||||||
|
resolve(viewer.loadDocumentNode(doc, doc.getRoot().getDefaultGeometry()));
|
||||||
|
}
|
||||||
|
function onDocumentLoadFailure(code, message, errors) {
|
||||||
|
reject({ code, message, errors });
|
||||||
|
}
|
||||||
|
viewer.setLightPreset(0);
|
||||||
|
Autodesk.Viewing.Document.load(
|
||||||
|
"urn:" + urn,
|
||||||
|
onDocumentLoadSuccess,
|
||||||
|
onDocumentLoadFailure
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const onToolbarCreated = (e) => {
|
||||||
|
const group = viewer.toolbar.getControl("settingsTools");
|
||||||
|
group.removeControl("toolbar-modelStructureTool");
|
||||||
|
group.removeControl("toolbar-propertiesTool");
|
||||||
|
//group.removeControl('toolbar-settingsTool');
|
||||||
|
//group.removeControl('toolbar-fullscreenTool');
|
||||||
|
viewer.removeEventListener(
|
||||||
|
Autodesk.Viewing.TOOLBAR_CREATED_EVENT,
|
||||||
|
onToolbarCreated
|
||||||
|
);
|
||||||
|
};
|
Loading…
Reference in a new issue