origin fixed

This commit is contained in:
zwnk 2024-11-24 04:52:39 -03:00
commit 01bfbd5767
22 changed files with 1890 additions and 0 deletions

4
.env_example Normal file
View file

@ -0,0 +1,4 @@
APS_CLIENT_ID=
APS_CLIENT_SECRET=
APS_BUCKET=
presetModel=BPZ_00_GRU_SIT_STR_MOD_EXI_Powerhouse.rvt

6
.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
node_modules
.env
*.log
.DS_Store
Thumbs.db
.vscode

11
Dockerfile Normal file
View file

@ -0,0 +1,11 @@
FROM node:alpine
# Create app directory
WORKDIR /usr/src/app
# Install app dependencies
COPY . .
RUN npm install
EXPOSE 8080
CMD ./start.sh

21
LICENSE Normal file
View 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.

202
README.md Normal file
View file

@ -0,0 +1,202 @@
<!--
*** 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.
<a href="https://webapps.gruner.ch">
<img src="img/thumbnail.png" alt="Logo" width="400">
</a>
<p align="right">(<a href="#readme-top">back to top</a>)</p>
<!-- GETTING STARTED -->
## Getting Started
The Viewer is setup to be deployed with docker. Nevertheless it can be run standalone by installing nodejs on the target machine.
If standalone, only the `.env` has to be created and populated. All other steps can be skipped.
## Setup procedure for dmzlxv09.dmz.gruner.ch aka webapps.gruner.ch
### Docker Setup
- clone the Repo
- Create a _.env_ file in the project folder (from .env_sample), and populae it with the snippet below,
replacing `<client-id>` and `<client-secret>` with your APS Client ID and Client Secret
- upload the model via [OSS Manager](https://oss-manager.autodesk.io/) and insert the filename of the model `presetModel= `
```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:808X
### 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
View 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
View file

@ -0,0 +1,11 @@
services:
app:
build: .
# volumes:
# - .:/usr/src/app
container_name: docker-aps
restart: always
ports:
- 127.0.0.1:8067:8080
command: npm run start
env_file: .env

BIN
img/gruner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
img/thumbnail.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 502 KiB

10
models.json Normal file
View file

@ -0,0 +1,10 @@
{
"revit_models": [
{
"filename": "Powerhouse.rvt"
},
{
"filename": "Administration_Building.rvt"
}
]
}

1177
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

24
package.json Normal file
View 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
View 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;

71
routes/models.js Normal file
View file

@ -0,0 +1,71 @@
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) {
try {
const models = require('../models.json');
res.json(models.revit_models);
} catch (err) {
next(err);
}
});
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
View 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
View 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, '');

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

BIN
wwwroot/gruner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

29
wwwroot/index.html Normal file
View file

@ -0,0 +1,29 @@
<!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
View 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;
}

41
wwwroot/main.js Normal file
View file

@ -0,0 +1,41 @@
import { initViewer } from "./viewer.js";
// 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 resp1 = await fetch("/api/models"); // api call to get all available models
const allmodels = await resp1.json();
const resp2 = await fetch("/api/selectedmodel"); // api call to get configured model name
const selectModelName = await resp2.json();
const selectedUrns = [];
for (let j = 0; j < selectModelName.length; j++) {
for (let i = 0; i < allmodels.length; i++) {
if (allmodels[i].name === selectModelName[j].filename) {
changeTitle("Model Viewer");
selectedUrns.push(allmodels[i].urn);
break; // Exit the inner loop if a match is found
}
}
}
return selectedUrns;
} catch (err) {
alert("Could not list models. See the console for more details.");
console.error(err);
}
}
async function start() {
const selModelss = await getModelsUrn();
// console.log("yoooooo", selModelss)
initViewer(document.getElementById("preview"), selModelss);
}
start();

89
wwwroot/viewer.js Normal file
View file

@ -0,0 +1,89 @@
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, urns) {
const viewerOptions = {
extensions: [],
};
Autodesk.Viewing.Initializer(
{ env: "AutodeskProduction", getAccessToken },
() => {
var viewer = new Autodesk.Viewing.Private.GuiViewer3D(
container,
viewerOptions
);
viewer.start();
viewer.addEventListener(
Autodesk.Viewing.TOOLBAR_CREATED_EVENT,
onToolbarCreated
);
urns.map((m) => {
addViewable(viewer, m);
});
}
);
}
async function addViewable(viewer, urn, xform, offset) {
return new Promise(function (resolve, reject) {
function onDocumentLoadSuccess(doc) {
const viewable = doc.getRoot().getDefaultGeometry();
const options = {
//preserveView: true,
keepCurrentModels: true,
modelSpace: true, // 2D drawings
applyRefPoint: true, // 3D shared coordinates
applyScaling: 'm', // force all models to same scale
globalOffset: {x:0,y:0,z:0}, // force all models to origin
//placementTransform: (new THREE.Matrix4()).setPosition(m.xform)
};
if (xform) {
options.placementTransform = xform;
}
if (offset) {
options.globalOffset = offset;
}
viewer
.loadDocumentNode(doc, viewable, options)
.then(resolve)
.catch(reject)
// .then(onToolbarCreated(viewer));
}
function onDocumentLoadFailure(code) {
console.error(`Document load failed with error code: ${code}`);
console.error(`Failed URN: ${urn}`);
// console.error(`Access Token: ${accessToken}`); // Log the access token if possible
reject(`Could not load document (${code}).`);
}
console.log(urn);
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
);
};