Compare commits
27 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1762205e67 | |||
| 6f6836ef51 | |||
| 54b812fa14 | |||
| f4bcb600bf | |||
| 6df659fbc5 | |||
| dd02f26d0b | |||
| c1e0155c18 | |||
| 8409552288 | |||
| e9fe4387cb | |||
| 0b4217465f | |||
| 73ff2abb3c | |||
| 377a785a19 | |||
| 08e6694bc9 | |||
| bf032646c4 | |||
| b67ca7a487 | |||
| 208fdb4ae3 | |||
| 2d9f179c14 | |||
| 92a1788364 | |||
| faac4ee821 | |||
| 164fc17edb | |||
| 6962e82ccd | |||
| 697906c5b5 | |||
| 0fb161f550 | |||
| c03e9d8262 | |||
| 34061fd12a | |||
| fcec358540 | |||
| 32eb5fb199 |
@@ -0,0 +1 @@
|
|||||||
|
web: uvicorn server.main:app --app-dir=server --host=0.0.0.0 --port=${PORT:-5000}
|
||||||
@@ -1,2 +1,111 @@
|
|||||||
# speckle-aectech-masterclass
|
<h1 align="center">
|
||||||
web app for comparing Speckle stream commits
|
<img src="https://user-images.githubusercontent.com/2679513/131189167-18ea5fe1-c578-47f6-9785-3748178e4312.png" width="150px"/><br/>
|
||||||
|
Speckle | AEC Tech Masterclass
|
||||||
|
</h1>
|
||||||
|
<h3 align="center">
|
||||||
|
A Speckle app to compare different commits and visualize the results
|
||||||
|
</h3>
|
||||||
|
<p align="center"><b>Speckle</b> is data infrastructure for the AEC industry.</p><br/>
|
||||||
|
|
||||||
|
<p align="center"><a href="https://twitter.com/SpeckleSystems"><img src="https://img.shields.io/twitter/follow/SpeckleSystems?style=social" alt="Twitter Follow"></a> <a href="https://speckle.community"><img src="https://img.shields.io/discourse/users?server=https%3A%2F%2Fspeckle.community&style=flat-square&logo=discourse&logoColor=white" alt="Community forum users"></a> <a href="https://speckle.systems"><img src="https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square" alt="website"></a> <a href="https://speckle.guide/dev/"><img src="https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&logo=read-the-docs&logoColor=white" alt="docs"></a></p>
|
||||||
|
|
||||||
|
# About Speckle
|
||||||
|
|
||||||
|
What is Speckle? Check our 
|
||||||
|
|
||||||
|
### Features
|
||||||
|
|
||||||
|
- **Object-based:** say goodbye to files! Speckle is the first object based platform for the AEC industry
|
||||||
|
- **Version control:** Speckle is the Git & Hub for geometry and BIM data
|
||||||
|
- **Collaboration:** share your designs collaborate with others
|
||||||
|
- **3D Viewer:** see your CAD and BIM models online, share and embed them anywhere
|
||||||
|
- **Interoperability:** get your CAD and BIM models into other software without exporting or importing
|
||||||
|
- **Real time:** get real time updates and notifications and changes
|
||||||
|
- **GraphQL API:** get what you need anywhere you want it
|
||||||
|
- **Webhooks:** the base for a automation and next-gen pipelines
|
||||||
|
- **Built for developers:** we are building Speckle with developers in mind and got tools for every stack
|
||||||
|
- **Built for the AEC industry:** Speckle connectors are plugins for the most common software used in the industry such as Revit, Rhino, Grasshopper, AutoCAD, Civil 3D, Excel, Unreal Engine, Unity, QGIS, Blender and more!
|
||||||
|
|
||||||
|
### Try Speckle now!
|
||||||
|
|
||||||
|
Give Speckle a try in no time by:
|
||||||
|
|
||||||
|
- [](https://speckle.xyz) ⇒ creating an account!
|
||||||
|
|
||||||
|
|
||||||
|
### Resources
|
||||||
|
|
||||||
|
- [](https://speckle.community) for help, feature requests or just to hang with other speckle enthusiasts, check out our community forum!
|
||||||
|
- [](https://speckle.systems) our tutorials portal is full of resources to get you started using Speckle
|
||||||
|
- [](https://speckle.guide/dev/) reference on almost any end-user and developer functionality
|
||||||
|
|
||||||
|
# AECTech Masterclass Repo
|
||||||
|
|
||||||
|
For the AECTech Masterclass, we're building a web application that **compares two 3D models and visualizes changes in geometry**. This repo is divided into a **web app** developed with Vue.js and a **server** built with python.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Frontend
|
||||||
|
|
||||||
|
The `frontend/` folder contains all code related to the app any user will be able to access.
|
||||||
|
|
||||||
|
The app was built using `Vue.js`, and communicates with Speckle through our `GraphQL API`. It also communicates with the app's `server` using `REST` calls.
|
||||||
|
|
||||||
|
## Backend
|
||||||
|
|
||||||
|
The `server/` folder contains all code related to the app's backend.
|
||||||
|
|
||||||
|
We're using a [FastAPI](https://link) server, written in Python, as well as our Python SDK to communicate with the [public Speckle server](https://speckle.xyz). The server contains two basic routes:
|
||||||
|
|
||||||
|
- **`diff-check/STREAM_ID/CURRENT_COMMIT_ID/PREV_COMMIT_ID`**
|
||||||
|
|
||||||
|
Checks for an existing commit in a predefined `diff` branch and returns the _diff commit_ if it does exist.
|
||||||
|
|
||||||
|
- **`diff/STREAM_ID/CURRENT_COMMIT_ID/PREV_COMMIT_ID`**
|
||||||
|
|
||||||
|
Performs a diff operation against two commits from the same stream. The result of the `diff` operation will be commited to the stream on a predefined diff branch, and returns the diff commit if the operation was successful.
|
||||||
|
|
||||||
|
# Workshop pre-requisites
|
||||||
|
|
||||||
|
Our workshop includes a **code walkthrough**, where we'll bring you line-by-line through a few key Speckle-specific front end and back end sections of our repo, as well as **live app testing**, where we'll use Speckle connectors in conjunction with our new web app to analyze some model geometry.
|
||||||
|
|
||||||
|
Prepare for both portions of our workshop by going through the following sections to brush up on essential knowledge and make sure your development and testing environments are set up!
|
||||||
|
|
||||||
|
## General requirements
|
||||||
|
|
||||||
|
- An IDE. We're using [VSCode](https://code.visualstudio.com/download) with the [Vetur extension](https://marketplace.visualstudio.com/items?itemName=octref.vetur)
|
||||||
|
- A Speckle account. Create one on our public server at [Speckle.xyz](https://speckle.xyz)
|
||||||
|
- A GitHub account: don't forget to clone this repo!
|
||||||
|
|
||||||
|
## Backend walkthrough requirements
|
||||||
|
|
||||||
|
Some basic familiarity with python, POST requests, and API routing is recommended.
|
||||||
|
|
||||||
|
- `python` (version `3.6`, recommended `3.9`) installed on your computer. [Download it here](https://www.python.org/downloads/)
|
||||||
|
- Install `FastAPI`:
|
||||||
|
```shell
|
||||||
|
pip install fastapi
|
||||||
|
# and then
|
||||||
|
pip install "uvicorn[standard]"
|
||||||
|
```
|
||||||
|
- Install `specklepy`
|
||||||
|
```shell
|
||||||
|
pip install specklepy
|
||||||
|
```
|
||||||
|
*If you're experiencing pip install issues, check that you are running these commands as an administrator!*
|
||||||
|
|
||||||
|
## Frontend walkthrough requirements
|
||||||
|
|
||||||
|
Some basic familiarity with JS and the Vue framework is recommended.
|
||||||
|
|
||||||
|
- Install `node` [Download it here](https://nodejs.org/en/download/)
|
||||||
|
- Install `vue CLI` - [Instructions here](https://cli.vuejs.org/guide/installation.html)
|
||||||
|
- Install `vue dev tools` for Chrome [here](https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd)
|
||||||
|
|
||||||
|
## App testing requirements
|
||||||
|
|
||||||
|
We're using [Rhino 7](https://www.rhino3d.com/download/), Grasshopper for Rhino, and Revit 2021 for our 3D model comparisons. You can
|
||||||
|
|
||||||
|
- Install [Speckle Manager](https://speckle.guide/user/manager.html)
|
||||||
|
- Read our [Speckle Rhino guide](https://speckle.guide/user/rhino.html) for a quick intro to using Speckle for Rhino 😎
|
||||||
|
- Install the Rhino `Speckle Connector` from Speckle Manager. If you'd like to play around with Grasshopper and Revit, you can install those too!
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
<h1 align="center">
|
|
||||||
<img src="https://user-images.githubusercontent.com/2679513/131189167-18ea5fe1-c578-47f6-9785-3748178e4312.png" width="150px"/><br/>
|
|
||||||
Speckle | Revit Dashboard
|
|
||||||
</h1>
|
|
||||||
<h3 align="center">
|
|
||||||
Speckle App to display Revit commits
|
|
||||||
</h3>
|
|
||||||
<p align="center"><b>Speckle</b> is data infrastructure for the AEC industry.</p><br/>
|
|
||||||
|
|
||||||
<p align="center"><a href="https://twitter.com/SpeckleSystems"><img src="https://img.shields.io/twitter/follow/SpeckleSystems?style=social" alt="Twitter Follow"></a> <a href="https://speckle.community"><img src="https://img.shields.io/discourse/users?
|
|
||||||
server=https%3A%2F%2Fspeckle.community&style=flat-square&logo=discourse&logoColor=white" alt="Community forum users"></a> <a href="https://speckle.systems"><img src="https://img.shields.io/badge/https://-speckle.systems-royalblue?style=flat-square" alt="website"></a> <a href="https://speckle.guide/dev/"><img src="https://img.shields.io/badge/docs-speckle.guide-orange?style=flat-square&logo=read-the-docs&logoColor=white" alt="docs"></a></p>
|
|
||||||
<p align="center"><a href="https://circleci.com/gh/specklesystems/speckle-sharp"><img src="https://circleci.com/gh/specklesystems/speckle-sharp.svg?style=svg" alt=".NET Core"></a></p>
|
|
||||||
|
|
||||||
# About Speckle
|
|
||||||
|
|
||||||
What is Speckle? Check our 
|
|
||||||
|
|
||||||
### Features
|
|
||||||
|
|
||||||
- **Object-based:** say goodbye to files! Speckle is the first object based platform for the AEC industry
|
|
||||||
- **Version control:** Speckle is the Git & Hub for geometry and BIM data
|
|
||||||
- **Collaboration:** share your designs collaborate with others
|
|
||||||
- **3D Viewer:** see your CAD and BIM models online, share and embed them anywhere
|
|
||||||
- **Interoperability:** get your CAD and BIM models into other software without exporting or importing
|
|
||||||
- **Real time:** get real time updates and notifications and changes
|
|
||||||
- **GraphQL API:** get what you need anywhere you want it
|
|
||||||
- **Webhooks:** the base for a automation and next-gen pipelines
|
|
||||||
- **Built for developers:** we are building Speckle with developers in mind and got tools for every stack
|
|
||||||
- **Built for the AEC industry:** Speckle connectors are plugins for the most common software used in the industry such as Revit, Rhino, Grasshopper, AutoCAD, Civil 3D, Excel, Unreal Engine, Unity, QGIS, Blender and more!
|
|
||||||
|
|
||||||
### Try Speckle now!
|
|
||||||
|
|
||||||
Give Speckle a try in no time by:
|
|
||||||
|
|
||||||
- [](https://speckle.xyz) ⇒ creating an account at
|
|
||||||
- [](https://marketplace.digitalocean.com/apps/speckle-server?refcode=947a2b5d7dc1) ⇒ deploying an instance in 1 click
|
|
||||||
|
|
||||||
### Resources
|
|
||||||
|
|
||||||
- [](https://speckle.community) for help, feature requests or just to hang with other speckle enthusiasts, check out our community forum!
|
|
||||||
- [](https://speckle.systems) our tutorials portal is full of resources to get you started using Speckle
|
|
||||||
- [](https://speckle.guide/dev/) reference on almost any end-user and developer functionality
|
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
# Repo structure
|
|
||||||
|
|
||||||
> TBD!!!
|
|
||||||
|
|
||||||
- `frontend/`
|
|
||||||
- `backend/`
|
|
||||||
|
|
||||||
### Other repos
|
|
||||||
|
|
||||||
Make sure to also check and ⭐️ these other Speckle repositories:
|
|
||||||
|
|
||||||
- [`speckle-sharp`](https://github.com/specklesystems/speckle-sharp): .NET SDK, tooling, schema and Connectors
|
|
||||||
- [`speckle-server`](https://github.com/specklesystems/speckle-server): Server and Web packages
|
|
||||||
- [`specklepy`](https://github.com/specklesystems/specklepy): Python SDK 🐍
|
|
||||||
- [`speckle-excel`](https://github.com/specklesystems/speckle-excel): Excel connector
|
|
||||||
- [`speckle-unity`](https://github.com/specklesystems/speckle-unity): Unity 3D connector
|
|
||||||
- [`speckle-blender`](https://github.com/specklesystems/speckle-blender): Blender connector
|
|
||||||
- [`speckle-unreal`](https://github.com/specklesystems/speckle-unreal): Unreal Engine Connector
|
|
||||||
- [`speckle-qgis`](https://github.com/specklesystems/speckle-qgis): QGIS connectod
|
|
||||||
- [`speckle-powerbi`](https://github.com/specklesystems/speckle-powerbi): PowerBi connector
|
|
||||||
- and more [connectos & tooling](https://github.com/specklesystems/)!
|
|
||||||
|
|
||||||
## Developing and Debugging
|
|
||||||
|
|
||||||
This app uses Vue.js 2. In order to run it locally
|
|
||||||
|
|
||||||
### Security
|
|
||||||
|
|
||||||
For any security vulnerabilities or concerns, please contact us directly at security[at]speckle.systems.
|
|
||||||
|
|
||||||
### License
|
|
||||||
|
|
||||||
Unless otherwise described, the code in this repository is licensed under the Apache-2.0 License. Please note that some modules, extensions or code herein might be otherwise licensed. This is indicated either in the root of the containing folder under a different license file, or in the respective file's header. If you have any questions, don't hesitate to get in touch with us via [email](mailto:hello@speckle.systems).
|
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"include": ["./src/**/*"]
|
||||||
|
}
|
||||||
Generated
+7
-6
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "speckle-revit-dashboard-app",
|
"name": "speckle-aec-tech-masterclass",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"lockfileVersion": 1,
|
"lockfileVersion": 1,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
@@ -11382,17 +11382,18 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"vue-eslint-parser": {
|
"vue-eslint-parser": {
|
||||||
"version": "7.6.0",
|
"version": "7.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.11.0.tgz",
|
||||||
"integrity": "sha512-QXxqH8ZevBrtiZMZK0LpwaMfevQi9UL7lY6Kcp+ogWHC88AuwUPwwCIzkOUc1LR4XsYAt/F9yHXAB/QoD17QXA==",
|
"integrity": "sha512-qh3VhDLeh773wjgNTl7ss0VejY9bMMa0GoDG2fQVyDzRFdiU3L7fw74tWZDHNQXdZqxO3EveQroa9ct39D2nqg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
"eslint-scope": "^5.0.0",
|
"eslint-scope": "^5.1.1",
|
||||||
"eslint-visitor-keys": "^1.1.0",
|
"eslint-visitor-keys": "^1.1.0",
|
||||||
"espree": "^6.2.1",
|
"espree": "^6.2.1",
|
||||||
"esquery": "^1.4.0",
|
"esquery": "^1.4.0",
|
||||||
"lodash": "^4.17.15"
|
"lodash": "^4.17.21",
|
||||||
|
"semver": "^6.3.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"eslint-scope": {
|
"eslint-scope": {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "speckle-revit-dashboard-app",
|
"name": "speckle-aec-tech-masterclass",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -83,16 +83,6 @@
|
|||||||
<v-icon small>mdi-account</v-icon>
|
<v-icon small>mdi-account</v-icon>
|
||||||
</v-list-item-icon>
|
</v-list-item-icon>
|
||||||
</v-list-item>
|
</v-list-item>
|
||||||
<v-list-item
|
|
||||||
link
|
|
||||||
href="https://speckle.systems/tutorials/revit-dash/"
|
|
||||||
target="_blank"
|
|
||||||
>
|
|
||||||
<v-list-item-title>Feedback</v-list-item-title>
|
|
||||||
<v-list-item-icon>
|
|
||||||
<v-icon small>mdi-message-alert-outline</v-icon>
|
|
||||||
</v-list-item-icon>
|
|
||||||
</v-list-item>
|
|
||||||
<v-list-item link @click="$store.dispatch('logout')">
|
<v-list-item link @click="$store.dispatch('logout')">
|
||||||
<v-list-item-title class="error--text">Log out</v-list-item-title>
|
<v-list-item-title class="error--text">Log out</v-list-item-title>
|
||||||
<v-list-item-icon>
|
<v-list-item-icon>
|
||||||
|
|||||||
@@ -1,137 +1,110 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<v-sheet class="pa-4" elevation="8">
|
<v-sheet class="pa-4" elevation="8">
|
||||||
<p>Compare commit</p>
|
<div class="d-flex align-center">
|
||||||
<v-row dense no-gutters>
|
<span style="white-space: nowrap;">
|
||||||
<v-col cols="6">
|
Compare commit
|
||||||
<v-slide-group v-model="commitA" center-active show-arrows>
|
<v-chip
|
||||||
<v-slide-item
|
:close="currentCommit != null"
|
||||||
v-for="n in commits"
|
@click:close="$emit('update:currentCommit', null)"
|
||||||
:key="n.id"
|
@click="show_commit_selector = 'A'"
|
||||||
v-slot="{ active, toggle }"
|
>
|
||||||
:value="n"
|
{{ currentCommit ? currentCommit.id : "Select commit" }}
|
||||||
>
|
|
||||||
<v-tooltip bottom>
|
|
||||||
<template v-slot:activator="{ on, attrs }">
|
|
||||||
<v-card
|
|
||||||
v-bind="attrs"
|
|
||||||
v-on="on"
|
|
||||||
:color="active ? 'success' : 'primary lighten-1'"
|
|
||||||
class="ma-1"
|
|
||||||
height="100"
|
|
||||||
width="40"
|
|
||||||
@click="toggle"
|
|
||||||
>
|
|
||||||
<div class="d-flex fill-height justify-center align-center">
|
|
||||||
<v-scale-transition mode="out-in">
|
|
||||||
<span v-if="!active" style="writing-mode: vertical-rl;">
|
|
||||||
{{ n.id }}
|
|
||||||
</span>
|
|
||||||
<v-icon
|
|
||||||
v-else
|
|
||||||
color="white"
|
|
||||||
size="20"
|
|
||||||
v-text="'mdi-close-circle-outline'"
|
|
||||||
></v-icon>
|
|
||||||
</v-scale-transition>
|
|
||||||
</div>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
<span>{{ n.message }}</span>
|
|
||||||
</v-tooltip>
|
|
||||||
</v-slide-item>
|
|
||||||
</v-slide-group>
|
|
||||||
</v-col>
|
|
||||||
<v-col cols="6">
|
|
||||||
<v-slide-group v-model="commitB" center-active show-arrows>
|
|
||||||
<v-slide-item
|
|
||||||
v-for="n in commits"
|
|
||||||
:key="n.id"
|
|
||||||
v-slot="{ active, toggle }"
|
|
||||||
:value="n"
|
|
||||||
:disabled="commitA && n.id === commitA['id']"
|
|
||||||
>
|
|
||||||
<v-tooltip bottom>
|
|
||||||
<template v-slot:activator="{ on, attrs }">
|
|
||||||
<v-card
|
|
||||||
v-bind="attrs"
|
|
||||||
v-on="on"
|
|
||||||
:color="active ? 'success' : 'primary lighten-1'"
|
|
||||||
class="ma-1"
|
|
||||||
height="100"
|
|
||||||
width="40"
|
|
||||||
@click="toggle"
|
|
||||||
:disabled="commitA && n.id === commitA['id']"
|
|
||||||
>
|
|
||||||
<div class="d-flex fill-height justify-center align-center">
|
|
||||||
<v-scale-transition mode="out-in">
|
|
||||||
<span v-if="!active" style="writing-mode: vertical-rl;">
|
|
||||||
{{ n.id }}
|
|
||||||
</span>
|
|
||||||
<v-icon
|
|
||||||
v-else
|
|
||||||
color="white"
|
|
||||||
size="20"
|
|
||||||
v-text="'mdi-close-circle-outline'"
|
|
||||||
></v-icon>
|
|
||||||
</v-scale-transition>
|
|
||||||
</div>
|
|
||||||
</v-card>
|
|
||||||
</template>
|
|
||||||
<span>{{ n.message }}</span>
|
|
||||||
</v-tooltip>
|
|
||||||
</v-slide-item>
|
|
||||||
</v-slide-group>
|
|
||||||
</v-col>
|
|
||||||
<div>
|
|
||||||
You are about to compare commit
|
|
||||||
<v-chip :close="commitA != null" @click:close="commitA = null">
|
|
||||||
{{ commitA ? commitA.id : "Select commit" }}
|
|
||||||
</v-chip>
|
</v-chip>
|
||||||
against
|
against
|
||||||
<v-chip :close="commitB != null" @click:close="commitB = null">
|
<v-chip
|
||||||
{{ commitB ? commitB.id : "Select commit" }}
|
:close="prevCommit != null"
|
||||||
|
@click:close="$emit('update:prevCommit', null)"
|
||||||
|
@click="show_commit_selector = 'B'"
|
||||||
|
>
|
||||||
|
{{ prevCommit ? prevCommit.id : "Select commit" }}
|
||||||
</v-chip>
|
</v-chip>
|
||||||
|
</span>
|
||||||
|
<div class="pl-2">
|
||||||
<v-btn
|
<v-btn
|
||||||
|
v-if="!diffCommit"
|
||||||
color="success"
|
color="success"
|
||||||
:disabled="!commitA || !commitB"
|
:disabled="!currentCommit || !prevCommit"
|
||||||
|
:loading="loading"
|
||||||
@click="requestDiff"
|
@click="requestDiff"
|
||||||
>
|
>
|
||||||
Run this!
|
Request diff
|
||||||
|
</v-btn>
|
||||||
|
<v-btn
|
||||||
|
v-else
|
||||||
|
:loading="loading"
|
||||||
|
color="primary"
|
||||||
|
@click="toggleDiffView"
|
||||||
|
>
|
||||||
|
{{ showDiff ? "View commits" : "View diff" }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</div>
|
</div>
|
||||||
</v-row>
|
</div>
|
||||||
|
<commit-slider
|
||||||
|
v-if="show_commit_selector == 'A'"
|
||||||
|
:commits="commits"
|
||||||
|
:selected="currentCommit"
|
||||||
|
:disabled-id="prevCommit ? prevCommit.id : null"
|
||||||
|
@update:selected="$emit('update:currentCommit', $event)"
|
||||||
|
@click:commit="show_commit_selector = null"
|
||||||
|
/>
|
||||||
|
<commit-slider
|
||||||
|
v-if="show_commit_selector == 'B'"
|
||||||
|
:commits="commits"
|
||||||
|
:selected="prevCommit"
|
||||||
|
:disabled-id="currentCommit ? currentCommit.id : null"
|
||||||
|
@update:selected="$emit('update:prevCommit', $event)"
|
||||||
|
@click:commit="show_commit_selector = null"
|
||||||
|
/>
|
||||||
</v-sheet>
|
</v-sheet>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script lang="js">
|
||||||
import { TOKEN } from "@/speckleUtils"
|
import { TOKEN } from "@/speckleUtils"
|
||||||
|
import CommitSlider from "@/components/commitSelector/CommitSlider.vue"
|
||||||
export default {
|
export default {
|
||||||
name: "CommitPanel",
|
name: "CommitPanel",
|
||||||
components: {},
|
components: { CommitSlider },
|
||||||
props: ["commits"],
|
props: ["commits", "diffCommit", "showDiff", "currentCommit", "prevCommit"],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
commitA: null,
|
show_commit_selector: null,
|
||||||
commitB: null
|
loading: false,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
async doesDiffExist(){
|
||||||
|
console.log("Checking if diff exists...")
|
||||||
|
// TODO: Missing implementation
|
||||||
|
return { commit: null }
|
||||||
|
},
|
||||||
async requestDiff() {
|
async requestDiff() {
|
||||||
console.log("diff requested for", this.commitA.id, this.commitB.id)
|
console.log("Requesting diff...")
|
||||||
var diffUrl = `http://localhost:8000/diff/${this.$route.params.id}/${this.commitA.id}/${this.commitB.id}`
|
// TODO: Missing implementation
|
||||||
console.log(diffUrl)
|
},
|
||||||
fetch(diffUrl, {
|
toggleDiffView(){
|
||||||
headers: {
|
this.$emit("update:showDiff", !this.showDiff)
|
||||||
method: "POST",
|
},
|
||||||
cors: "no-cors",
|
async handleCommitChange(event, value){
|
||||||
Authorisation: `Bearer ${localStorage.getItem(TOKEN)}`,
|
this.loading = true
|
||||||
"Content-type": "application/json",
|
this.$emit(event, value)
|
||||||
"Access-Control-Allow-Origin": "*"
|
this.$emit("update:diff-commit", null)
|
||||||
}
|
if(value){
|
||||||
})
|
var diffRes = await this.doesDiffExist()
|
||||||
.then(async res => console.log("fetch success", await res.json(), res))
|
this.$emit("update:diff-commit", diffRes.commit)
|
||||||
.catch(err => console.warn("fetch failed", err))
|
}
|
||||||
|
this.loading = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
currentCommit: {
|
||||||
|
handler: async function(newVal, oldVal) {
|
||||||
|
this.handleCommitChange("current-commit", newVal)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
prevCommit: {
|
||||||
|
handler: async function(newVal, oldVal){
|
||||||
|
this.handleCommitChange("prev-commit", newVal)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,85 @@
|
|||||||
|
<template lang="html">
|
||||||
|
<v-slide-group
|
||||||
|
:value="selected"
|
||||||
|
@change="$emit('update:selected', $event)"
|
||||||
|
show-arrows
|
||||||
|
>
|
||||||
|
<v-slide-item
|
||||||
|
v-for="n in commits"
|
||||||
|
:key="n.id"
|
||||||
|
v-slot="{ active, toggle }"
|
||||||
|
:value="n"
|
||||||
|
>
|
||||||
|
<v-tooltip bottom>
|
||||||
|
<template v-slot:activator="{ on, attrs }">
|
||||||
|
<v-card
|
||||||
|
v-bind="attrs"
|
||||||
|
v-on="on"
|
||||||
|
:color="active ? 'primary' : 'grey lighten-1'"
|
||||||
|
:disabled="n.id === disabledId"
|
||||||
|
:elevation="0"
|
||||||
|
class="ma-1"
|
||||||
|
@click="toggle"
|
||||||
|
v-on:click="$emit('click:commit', n)"
|
||||||
|
>
|
||||||
|
<div class="d-flex fill-height justify-center align-center">
|
||||||
|
<v-scale-transition mode="out-in">
|
||||||
|
<div
|
||||||
|
class="d-flex flex-column justify-center align-center white--text ma-3"
|
||||||
|
v-if="!active"
|
||||||
|
>
|
||||||
|
<v-avatar :size="30" class="mb-2">
|
||||||
|
<img :src="n.authorAvatar" />
|
||||||
|
</v-avatar>
|
||||||
|
<v-chip small color="primary" class="mb-4">
|
||||||
|
{{ appInitials(n.sourceApplication) }}
|
||||||
|
</v-chip>
|
||||||
|
<span style="writing-mode: vertical-rl;">
|
||||||
|
<timeago :datetime="n.createdAt"></timeago>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div v-else class="ma-3">
|
||||||
|
<v-icon
|
||||||
|
color="white"
|
||||||
|
size="24"
|
||||||
|
v-text="'mdi-close-circle-outline'"
|
||||||
|
></v-icon>
|
||||||
|
</div>
|
||||||
|
</v-scale-transition>
|
||||||
|
</div>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
<span>{{ n.authorName }} — {{ n.message }}</span>
|
||||||
|
</v-tooltip>
|
||||||
|
</v-slide-item>
|
||||||
|
</v-slide-group>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
props: ["selected", "commits", "disabledId"],
|
||||||
|
methods: {
|
||||||
|
appInitials(sourceApplication) {
|
||||||
|
console.log(sourceApplication)
|
||||||
|
switch (sourceApplication) {
|
||||||
|
case "Rhino6":
|
||||||
|
case "Rhino7":
|
||||||
|
return "RH"
|
||||||
|
case "Revit2019":
|
||||||
|
case "Revit2020":
|
||||||
|
case "Revit2021":
|
||||||
|
case "Revit2022":
|
||||||
|
return "RVT"
|
||||||
|
case sourceApplication.startsWith("Autocad"):
|
||||||
|
return "ACAD"
|
||||||
|
case "Grasshopper":
|
||||||
|
return "GH"
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style></style>
|
||||||
@@ -221,8 +221,8 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
objectUrls: {
|
objectUrl: {
|
||||||
type: Array,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
unloadTrigger: {
|
unloadTrigger: {
|
||||||
@@ -277,7 +277,9 @@ export default {
|
|||||||
this.namedViews.push(...views)
|
this.namedViews.push(...views)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
objectUrls() {
|
objectUrl(newVal, oldVal) {
|
||||||
|
if (newVal == oldVal) return
|
||||||
|
console.log("obj url changed", newVal, oldVal)
|
||||||
this.unloadData()
|
this.unloadData()
|
||||||
this.load()
|
this.load()
|
||||||
}
|
}
|
||||||
@@ -289,7 +291,6 @@ export default {
|
|||||||
if (!this.viewer) {
|
if (!this.viewer) {
|
||||||
this.viewer = new Viewer({ container: this.$refs.renderer })
|
this.viewer = new Viewer({ container: this.$refs.renderer })
|
||||||
}
|
}
|
||||||
|
|
||||||
this.viewer.onWindowResize()
|
this.viewer.onWindowResize()
|
||||||
this.setupEvents()
|
this.setupEvents()
|
||||||
},
|
},
|
||||||
@@ -338,12 +339,11 @@ export default {
|
|||||||
})
|
})
|
||||||
},
|
},
|
||||||
load() {
|
load() {
|
||||||
if (!this.objectUrls || this.objectUrls.length === 0) return
|
if (!this.objectUrl) return
|
||||||
this.viewer.onWindowResize()
|
|
||||||
this.objectUrls?.forEach(url => {
|
this.viewer.loadObject(this.objectUrl, localStorage.getItem(TOKEN))
|
||||||
this.viewer.loadObject(url, localStorage.getItem(TOKEN))
|
this.viewerLastLoadedUrl = this.objectUrl
|
||||||
this.viewerLastLoadedUrl = url
|
|
||||||
})
|
|
||||||
this.setupEvents()
|
this.setupEvents()
|
||||||
this.hasLoadedModel = true
|
this.hasLoadedModel = true
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
query {
|
|
||||||
user {
|
|
||||||
avatar
|
|
||||||
id
|
|
||||||
name
|
|
||||||
email
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -14,17 +14,7 @@ const routes = [
|
|||||||
component: Home,
|
component: Home,
|
||||||
meta: {
|
meta: {
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
title: "Speckle Revit Dashboard",
|
title: "Speckle AEC Tech Masterclass"
|
||||||
metaTags: [
|
|
||||||
{
|
|
||||||
name: "description",
|
|
||||||
content: "The speckle Revit Dashboard homepage"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
property: "og:description",
|
|
||||||
content: "The speckle Revit Dashboard homepage"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -33,7 +23,7 @@ const routes = [
|
|||||||
component: WelcomeView,
|
component: WelcomeView,
|
||||||
meta: {
|
meta: {
|
||||||
requiresNoAuth: true,
|
requiresNoAuth: true,
|
||||||
title: "Login | Speckle Revit Dashboard"
|
title: "Login | Speckle AEC Tech Masterclass"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -57,6 +47,7 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
if (to.meta.title) {
|
if (to.meta.title) {
|
||||||
document.title = to.meta.title
|
document.title = to.meta.title
|
||||||
}
|
}
|
||||||
|
|
||||||
if (to.query.access_code) {
|
if (to.query.access_code) {
|
||||||
// If the route contains an access code, exchange it
|
// If the route contains an access code, exchange it
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -1,18 +1,5 @@
|
|||||||
|
// TODO: Write user + server info query
|
||||||
|
export const userInfoQuery = ``
|
||||||
export const userInfoQuery = `
|
|
||||||
query {
|
|
||||||
user {
|
|
||||||
name
|
|
||||||
id
|
|
||||||
avatar
|
|
||||||
email
|
|
||||||
},
|
|
||||||
serverInfo {
|
|
||||||
name
|
|
||||||
company
|
|
||||||
}
|
|
||||||
}`
|
|
||||||
|
|
||||||
export const streamCommitsQuery = `
|
export const streamCommitsQuery = `
|
||||||
query($id: String!, $limit: Int, $cursor: String) {
|
query($id: String!, $limit: Int, $cursor: String) {
|
||||||
@@ -20,17 +7,20 @@ export const streamCommitsQuery = `
|
|||||||
name
|
name
|
||||||
updatedAt
|
updatedAt
|
||||||
id
|
id
|
||||||
commits(limit: $limit, cursor: $cursor) {
|
branch(name: "main"){
|
||||||
totalCount
|
commits(limit: $limit, cursor: $cursor) {
|
||||||
cursor
|
totalCount
|
||||||
items{
|
cursor
|
||||||
id
|
items{
|
||||||
message
|
id
|
||||||
branchName
|
message
|
||||||
sourceApplication
|
branchName
|
||||||
referencedObject
|
sourceApplication
|
||||||
authorName
|
referencedObject
|
||||||
createdAt
|
authorName
|
||||||
|
authorAvatar
|
||||||
|
createdAt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -59,7 +49,6 @@ export const streamObjectQuery = `query($streamId: String!, $objectId: String!)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|
||||||
export const latestStreamsQuery = `query {
|
export const latestStreamsQuery = `query {
|
||||||
streams(limit: 10){
|
streams(limit: 10){
|
||||||
cursor
|
cursor
|
||||||
@@ -72,4 +61,4 @@ export const latestStreamsQuery = `query {
|
|||||||
updatedAt
|
updatedAt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import {
|
|||||||
streamObjectQuery,
|
streamObjectQuery,
|
||||||
streamSearchQuery,
|
streamSearchQuery,
|
||||||
userInfoQuery
|
userInfoQuery
|
||||||
} from "@/speckleQueries";
|
} from "@/speckleQueries"
|
||||||
|
|
||||||
export const APP_NAME = process.env.VUE_APP_SPECKLE_NAME
|
export const APP_NAME = process.env.VUE_APP_SPECKLE_NAME
|
||||||
export const SERVER_URL = process.env.VUE_APP_SERVER_URL
|
export const SERVER_URL = process.env.VUE_APP_SERVER_URL
|
||||||
@@ -14,43 +14,21 @@ export const CHALLENGE = `${APP_NAME}.Challenge`
|
|||||||
|
|
||||||
// Redirects to the Speckle server authentication page, using a randomly generated challenge. Challenge will be stored to compare with when exchanging the access code.
|
// Redirects to the Speckle server authentication page, using a randomly generated challenge. Challenge will be stored to compare with when exchanging the access code.
|
||||||
export function goToSpeckleAuthPage() {
|
export function goToSpeckleAuthPage() {
|
||||||
// Generate random challenge
|
console.log("Redirecting to Speckle Auth page...")
|
||||||
var challenge = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15)
|
// TODO: Missing implementation
|
||||||
// Save challenge in localStorage
|
|
||||||
localStorage.setItem(CHALLENGE, challenge)
|
|
||||||
// Send user to auth page
|
|
||||||
window.location = `${SERVER_URL}/authn/verify/${process.env.VUE_APP_SPECKLE_ID}/${challenge}`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log out the current user. This removes the token/refreshToken pair.
|
// Log out the current user. This removes the token/refreshToken pair.
|
||||||
export function speckleLogOut() {
|
export function speckleLogOut() {
|
||||||
// Remove both token and refreshToken from localStorage
|
// Remove both token and refreshToken from localStorage
|
||||||
localStorage.removeItem(TOKEN)
|
console.log("Logging out user")
|
||||||
localStorage.removeItem(REFRESH_TOKEN)
|
// TODO: Missing implementation
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exchanges the provided access code with a token/refreshToken pair, and saves them to local storage.
|
// Exchanges the provided access code with a token/refreshToken pair, and saves them to local storage.
|
||||||
export async function exchangeAccessCode(accessCode) {
|
export async function exchangeAccessCode(accessCode) {
|
||||||
var res = await fetch(`${SERVER_URL}/auth/token/`, {
|
console.log("Exchanging access code...")
|
||||||
method: 'POST',
|
// TODO: Missing implementation
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
accessCode: accessCode,
|
|
||||||
appId: process.env.VUE_APP_SPECKLE_ID,
|
|
||||||
appSecret: process.env.VUE_APP_SPECKLE_SECRET,
|
|
||||||
challenge: localStorage.getItem(CHALLENGE)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
var data = await res.json()
|
|
||||||
if (data.token) {
|
|
||||||
// If retrieving the token was successful, remove challenge and set the new token and refresh token
|
|
||||||
localStorage.removeItem(CHALLENGE)
|
|
||||||
localStorage.setItem(TOKEN, data.token)
|
|
||||||
localStorage.setItem(REFRESH_TOKEN, data.refreshToken)
|
|
||||||
}
|
|
||||||
return data
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calls the GraphQL endpoint of the Speckle server with a specific query.
|
// Calls the GraphQL endpoint of the Speckle server with a specific query.
|
||||||
@@ -58,38 +36,41 @@ export async function speckleFetch(query, vars) {
|
|||||||
let token = localStorage.getItem(TOKEN)
|
let token = localStorage.getItem(TOKEN)
|
||||||
if (token)
|
if (token)
|
||||||
try {
|
try {
|
||||||
var res = await fetch(
|
var res = await fetch(`${SERVER_URL}/graphql`, {
|
||||||
`${SERVER_URL}/graphql`,
|
method: "POST",
|
||||||
{
|
headers: {
|
||||||
method: 'POST',
|
Authorization: "Bearer " + token,
|
||||||
headers: {
|
"Content-Type": "application/json"
|
||||||
'Authorization': 'Bearer ' + token,
|
},
|
||||||
'Content-Type': 'application/json'
|
body: JSON.stringify({
|
||||||
},
|
query: query,
|
||||||
body: JSON.stringify({
|
variables: vars || null
|
||||||
query: query,
|
|
||||||
variables: vars || null
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
})
|
||||||
return await res.json()
|
return await res.json()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error("API call failed", err)
|
console.error("API call failed", err)
|
||||||
}
|
}
|
||||||
else
|
else return Promise.reject("You are not logged in (token does not exist)")
|
||||||
return Promise.reject("You are not logged in (token does not exist)")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch the current user data using the userInfoQuery
|
// Fetch the current user data using the userInfoQuery
|
||||||
export const getUserData = () => speckleFetch(userInfoQuery)
|
export const getUserData = () => speckleFetch(userInfoQuery)
|
||||||
|
|
||||||
// Fetch for streams matching the specified text using the streamSearchQuery
|
// Fetch for streams matching the specified text using the streamSearchQuery
|
||||||
export const searchStreams = (e) => speckleFetch(streamSearchQuery, {searchText: e})
|
export const searchStreams = e =>
|
||||||
|
speckleFetch(streamSearchQuery, { searchText: e })
|
||||||
|
|
||||||
// Get commits related to a specific stream, allows for pagination by passing a cursor
|
// Get commits related to a specific stream, allows for pagination by passing a cursor
|
||||||
export const getStreamCommits = (streamId, itemsPerPage, cursor) => speckleFetch(streamCommitsQuery, {id: streamId, cursor, limit: itemsPerPage})
|
export const getStreamCommits = (streamId, itemsPerPage, cursor) =>
|
||||||
|
speckleFetch(streamCommitsQuery, {
|
||||||
|
id: streamId,
|
||||||
|
cursor,
|
||||||
|
limit: itemsPerPage
|
||||||
|
})
|
||||||
|
|
||||||
export const getStreamObject = (streamId, objectId) => speckleFetch(streamObjectQuery, {streamId, objectId}).then(res => res.data?.stream?.object?.data)
|
export const getObject = (streamId, objectId) =>
|
||||||
|
speckleFetch(streamObjectQuery, { streamId, objectId })
|
||||||
|
|
||||||
export const getObject = (streamId, objectId) => speckleFetch(streamObjectQuery, {streamId, objectId})
|
export const getStreams = () =>
|
||||||
|
speckleFetch(latestStreamsQuery).then(res => res.data?.streams)
|
||||||
export const getStreams = () => speckleFetch(latestStreamsQuery).then(res => res.data?.streams)
|
|
||||||
|
|||||||
@@ -1,24 +1,23 @@
|
|||||||
import Vue from 'vue'
|
import Vue from "vue"
|
||||||
import Vuex from 'vuex'
|
import Vuex from "vuex"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
exchangeAccessCode,
|
exchangeAccessCode,
|
||||||
getUserData,
|
getUserData,
|
||||||
goToSpeckleAuthPage,
|
goToSpeckleAuthPage,
|
||||||
speckleLogOut
|
speckleLogOut
|
||||||
} from "@/speckleUtils";
|
} from "@/speckleUtils"
|
||||||
import router from "@/router";
|
import router from "@/router"
|
||||||
|
|
||||||
Vue.use(Vuex)
|
Vue.use(Vuex)
|
||||||
|
|
||||||
|
|
||||||
export default new Vuex.Store({
|
export default new Vuex.Store({
|
||||||
state: {
|
state: {
|
||||||
user: null,
|
user: null,
|
||||||
serverInfo: null
|
serverInfo: null
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
isAuthenticated: (state) => state.user != null
|
isAuthenticated: state => state.user != null
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
setUser(state, user) {
|
setUser(state, user) {
|
||||||
|
|||||||
+21
-10
@@ -1,16 +1,29 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<v-container fluid fill-height class="home flex-column justify-center align-center primary--text">
|
<v-container
|
||||||
|
fluid
|
||||||
|
fill-height
|
||||||
|
class="home flex-column justify-center align-center primary--text"
|
||||||
|
>
|
||||||
<h1>Hi {{ $store.state.user.name }}!!</h1>
|
<h1>Hi {{ $store.state.user.name }}!!</h1>
|
||||||
<p>Search for a stream in the navigation bar, or pick from one of your latest 👇🏼</p>
|
<p>
|
||||||
|
Search for a stream in the navigation bar, or pick from one of your latest
|
||||||
|
👇🏼
|
||||||
|
</p>
|
||||||
<v-list v-if="streams" max-height="210px" class="overflow-y-auto">
|
<v-list v-if="streams" max-height="210px" class="overflow-y-auto">
|
||||||
<v-list-item-group>
|
<v-list-item-group>
|
||||||
<v-list-item v-for="stream in streams.items" :key="stream.id" @click="$router.push(`/streams/${stream.id}`)">
|
<v-list-item
|
||||||
|
v-for="stream in streams.items"
|
||||||
|
:key="stream.id"
|
||||||
|
@click="$router.push(`/streams/${stream.id}`)"
|
||||||
|
>
|
||||||
<v-list-item-content>
|
<v-list-item-content>
|
||||||
<v-list-item-title>
|
<v-list-item-title>
|
||||||
<v-row class="pa-0 ma-0">
|
<v-row class="pa-0 ma-0">
|
||||||
{{ stream.name }}
|
{{ stream.name }}
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<span class="primary rounded white--text pl-1 pr-1 caption">{{ stream.id }}</span>
|
<span class="primary rounded white--text pl-1 pr-1 caption">
|
||||||
|
{{ stream.id }}
|
||||||
|
</span>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-list-item-title>
|
</v-list-item-title>
|
||||||
<v-list-item-subtitle class="caption primary--text">
|
<v-list-item-subtitle class="caption primary--text">
|
||||||
@@ -25,11 +38,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import { getStreams } from "@/speckleUtils"
|
||||||
import {getStreams, TOKEN} from "@/speckleUtils";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Home',
|
name: "Home",
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
streams: null
|
streams: null
|
||||||
@@ -38,8 +50,7 @@ export default {
|
|||||||
async mounted() {
|
async mounted() {
|
||||||
this.streams = await getStreams()
|
this.streams = await getStreams()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -51,4 +62,4 @@ export default {
|
|||||||
.v-data-footer__select {
|
.v-data-footer__select {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,65 +1,42 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<v-container fill-height fluid class="pa-0">
|
<v-container fill-height fluid class="pa-0 grey lighten-3">
|
||||||
<div class="float-center-top">
|
TODO: Code up the stream diff view!!!
|
||||||
<CommitPanel v-if="stream" :commits="stream.commits.items"></CommitPanel>
|
|
||||||
</div>
|
|
||||||
<v-row class="fill-height" no-gutters>
|
|
||||||
<v-col fill-height cols="6">
|
|
||||||
<Renderer
|
|
||||||
v-if="stream"
|
|
||||||
:object-urls="[objectUrl(0)]"
|
|
||||||
show-selection-helper
|
|
||||||
></Renderer>
|
|
||||||
</v-col>
|
|
||||||
<v-col fill-height cols="6">
|
|
||||||
<Renderer
|
|
||||||
v-if="stream"
|
|
||||||
:object-urls="[objectUrl(1)]"
|
|
||||||
show-selection-helper
|
|
||||||
></Renderer>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { getStreamCommits, getStreamObject } from "@/speckleUtils"
|
import { getStreamCommits } from "@/speckleUtils"
|
||||||
import Renderer from "../components/viewer/Renderer.vue"
|
import Renderer from "../components/viewer/Renderer.vue"
|
||||||
import CommitPanel from "@/components/commitSelector/CommitPanel.vue"
|
import CommitPanel from "@/components/commitSelector/CommitPanel.vue"
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "StreamView",
|
name: "StreamView",
|
||||||
components: { Renderer, CommitPanel },
|
components: {},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
stream: null,
|
stream: null,
|
||||||
selectedCommit: null,
|
currentCommit: null,
|
||||||
refObj: null,
|
prevCommit: null,
|
||||||
|
diffCommit: null,
|
||||||
serverUrl: process.env.VUE_APP_SERVER_URL,
|
serverUrl: process.env.VUE_APP_SERVER_URL,
|
||||||
loading: true,
|
showDiff: false
|
||||||
progress: 0
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
if (this.streamId) {
|
//TODO: Get stream when mounting view
|
||||||
this.getStream()
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
/** @return {string} */
|
||||||
streamId() {
|
streamId() {
|
||||||
return this.$route.params.id
|
return this.$route.params.id
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async getStream() {
|
async getStream() {
|
||||||
var res = await getStreamCommits(this.streamId, 10, null)
|
//TODO: Missing implementation
|
||||||
this.selectedCommit = res.data.stream.commits.items[0]
|
|
||||||
this.stream = res.data.stream
|
|
||||||
},
|
},
|
||||||
objectUrl(i) {
|
objectUrl(commit) {
|
||||||
return [
|
//TODO: Missing implementation
|
||||||
`${this.serverUrl}/streams/${this.stream.id}/objects/${this.stream.commits.items[i].referencedObject}`
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
@@ -67,14 +44,6 @@ export default {
|
|||||||
handler: async function(val, oldVal) {
|
handler: async function(val, oldVal) {
|
||||||
if (val) this.getStream()
|
if (val) this.getStream()
|
||||||
}
|
}
|
||||||
},
|
|
||||||
selectedCommit: {
|
|
||||||
handler: async function() {
|
|
||||||
this.refObj = await getStreamObject(
|
|
||||||
this.stream.id,
|
|
||||||
this.selectedCommit.referencedObject
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,19 @@
|
|||||||
<template lang="html">
|
<template lang="html">
|
||||||
<v-container fill-height class="home flex-column justify-center align-center primary--text">
|
<v-container
|
||||||
|
fill-height
|
||||||
|
class="home flex-column justify-center align-center primary--text"
|
||||||
|
>
|
||||||
<v-img src="@/assets/logo.png" max-height="140px" max-width="140px"></v-img>
|
<v-img src="@/assets/logo.png" max-height="140px" max-width="140px"></v-img>
|
||||||
<h1>Welcome to the Speckle Revit Dashboard</h1>
|
<h1>Welcome to the Speckle Diff Visualizer</h1>
|
||||||
<p>This app allows you to analyse the data sent from Revit to Speckle.</p>
|
<p>This app generates a heat map diff of changed geometry.</p>
|
||||||
<v-alert type="info" text color="primary">
|
<v-alert type="info" text color="primary">
|
||||||
Check out the <a href="https://speckle.systems/blog" target="_blank">blog post</a> for more info!
|
This app is part of the AEC Tech Masterclass
|
||||||
</v-alert>
|
</v-alert>
|
||||||
<p class="grey--text">Please log in to access you Speckle data.</p>
|
<p class="grey--text">Please log in to access you Speckle data.</p>
|
||||||
</v-container>
|
</v-container>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'WelcomeView'
|
name: "WelcomeView"
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
fastapi==0.70.0
|
||||||
|
specklepy==2.4.0
|
||||||
|
uvicorn==0.15.0
|
||||||
|
aiofiles==0.6.0
|
||||||
|
python-multipart==0.0.5
|
||||||
|
jinja2==2.11.2
|
||||||
+55
-14
@@ -1,33 +1,74 @@
|
|||||||
from typing import Optional
|
"""FastAPI Backend for the AEC Tech Masterclass"""
|
||||||
from fastapi import FastAPI, Request
|
|
||||||
from mesh_diff import compare_meshes
|
import os
|
||||||
|
from fastapi import FastAPI, Request, HTTPException
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from mesh_diff import SpeckleMeshDiff
|
||||||
|
|
||||||
|
|
||||||
app = FastAPI()
|
app = FastAPI()
|
||||||
|
|
||||||
|
server_url = os.environ.get("SPECKLE_SERVER", "https://speckle.xyz")
|
||||||
|
diff_branch = os.environ.get("DIFF_BRANCH", "diff")
|
||||||
|
frontend_url = os.environ.get("FRONTEND_URL", "http://localhost:8080")
|
||||||
origins = [
|
origins = [
|
||||||
"http://localhost",
|
"http://localhost",
|
||||||
"http://localhost:8080",
|
"http://localhost:8080",
|
||||||
|
"https://speckle-aectech-masterclass.netlify.app",
|
||||||
|
"http://speckle-aectech-masterclass.netlify.app"
|
||||||
]
|
]
|
||||||
|
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_origins=origins,
|
allow_origins=["*"],
|
||||||
allow_credentials=True,
|
allow_credentials=True,
|
||||||
allow_methods=["*"],
|
allow_methods=["*"],
|
||||||
allow_headers=["*"],
|
allow_headers=["*"],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/")
|
|
||||||
def read_root():
|
|
||||||
return {"Hello": "World"}
|
|
||||||
|
|
||||||
|
|
||||||
@app.get("/diff/{stream_id}/{commit_current}/{commit_previous}")
|
@app.get("/diff/{stream_id}/{commit_current}/{commit_previous}")
|
||||||
def get_diff(stream_id: str, commit_current: str, commit_previous: str, request: Request):
|
def get_diff(stream_id: str, commit_current: str, commit_previous: str, request: Request):
|
||||||
print(request)
|
"""Diffing endpoint"""
|
||||||
print(request.headers.get("Authorisation"))
|
auth_header = request.headers.get("Authorisation")
|
||||||
token = request.headers.get("Authorisation").split(" ")[1]
|
|
||||||
print(token)
|
if auth_header is None:
|
||||||
return compare_meshes(stream_id, commit_current, commit_previous, token)
|
raise HTTPException(405, "No token provided")
|
||||||
|
|
||||||
|
token = auth_header.split(" ")[1]
|
||||||
|
|
||||||
|
try:
|
||||||
|
mesh_differ = SpeckleMeshDiff(token, server_url, diff_branch)
|
||||||
|
diff_commit = mesh_differ.process_diff(
|
||||||
|
stream_id, commit_current, commit_previous)
|
||||||
|
except Exception as e:
|
||||||
|
print(str(e))
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
return {"commit": diff_commit}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/diff_check/{stream_id}/{commit_current}/{commit_previous}")
|
||||||
|
def get_diff_check(stream_id: str, commit_current: str, commit_previous: str, request: Request):
|
||||||
|
"""Diffing endpoint"""
|
||||||
|
auth_header = request.headers.get("Authorisation")
|
||||||
|
|
||||||
|
if auth_header is None:
|
||||||
|
raise HTTPException(405, "No token provided")
|
||||||
|
|
||||||
|
token = auth_header.split(" ")[1]
|
||||||
|
|
||||||
|
try:
|
||||||
|
mesh_differ = SpeckleMeshDiff(token, server_url, diff_branch)
|
||||||
|
mesh_differ.stream_id = stream_id
|
||||||
|
mesh_differ.commit_current = commit_current
|
||||||
|
mesh_differ.commit_prev = commit_previous
|
||||||
|
existing_diff_commit = mesh_differ.check_existing_commits()
|
||||||
|
if existing_diff_commit is not None:
|
||||||
|
|
||||||
|
return {"exists": True, "commit": existing_diff_commit}
|
||||||
|
else:
|
||||||
|
return {"exists": False, "commit": None}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(str(e))
|
||||||
|
raise HTTPException(500, str(e))
|
||||||
|
|||||||
+259
-248
@@ -1,290 +1,301 @@
|
|||||||
from array import array
|
"""SpeckleMeshDiff for AEC Tech Masterclass"""
|
||||||
from hashlib import new
|
|
||||||
from logging import NullHandler
|
|
||||||
from types import AsyncGeneratorType
|
|
||||||
from typing import Any, List
|
|
||||||
from math import floor
|
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
import math
|
||||||
from specklepy.api import operations
|
from specklepy.api import operations
|
||||||
from specklepy.api.client import SpeckleClient
|
from specklepy.api.client import SpeckleClient
|
||||||
from specklepy.api.credentials import get_default_account
|
|
||||||
from specklepy.api.models import Branch
|
from specklepy.api.models import Branch
|
||||||
from specklepy.api.resources import stream
|
|
||||||
from specklepy.transports.server import ServerTransport
|
from specklepy.transports.server import ServerTransport
|
||||||
from specklepy.objects.geometry import GEOMETRY, Box, Brep, Point, Mesh, Line
|
from specklepy.objects.geometry import Brep, Point, Mesh
|
||||||
from specklepy.objects import Base
|
from specklepy.objects import Base
|
||||||
from specklepy.objects.other import RenderMaterial
|
from specklepy.objects.other import RenderMaterial
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
URL = 'https://speckle.xyz/streams/'
|
|
||||||
HOST = "latest.speckle.dev"
|
|
||||||
STREAM_ID = "8325294b8f"
|
|
||||||
COMMIT_ID = "c207299871" # current commit
|
|
||||||
PREV_COMMIT_ID = "b9f376d75d" # previous commit
|
|
||||||
DIFF_BRANCH = "diff"
|
|
||||||
COLORS = [-6426, -13108, -19790, -26215, -
|
COLORS = [-6426, -13108, -19790, -26215, -
|
||||||
32640, -39322, -45747, -52429, -59111, -65536]
|
32640, -39322, -45747, -52429, -59111, -65536]
|
||||||
WHITE = -1
|
WHITE = -1
|
||||||
|
|
||||||
|
|
||||||
def get_authenticated_client(token: str) -> SpeckleClient:
|
class SpeckleMeshDiff:
|
||||||
client = SpeckleClient(host=HOST)
|
"""Class to handle diffing between commits in a stream."""
|
||||||
client.authenticate(token=token)
|
|
||||||
return client
|
|
||||||
|
|
||||||
|
client: SpeckleClient = None
|
||||||
|
host: str = None
|
||||||
|
diff_branch: str = None
|
||||||
|
|
||||||
def receive_data(
|
commit_prev: str = None
|
||||||
client: SpeckleClient, stream_id: str, commit_id: str
|
commit_current: str = None
|
||||||
) -> Any:
|
stream_id: str = None
|
||||||
transport = ServerTransport(client, stream_id)
|
|
||||||
|
|
||||||
commit = client.commit.get(stream_id, commit_id)
|
def __init__(self, token: str, host: str = "https://speckle.xyz", diff_branch: str = "diff"):
|
||||||
res = operations.receive(commit.referencedObject, transport)
|
self.host = host
|
||||||
|
self.diff_branch = diff_branch
|
||||||
|
self.client = SpeckleClient(host=self.host)
|
||||||
|
self.client.authenticate(token=token)
|
||||||
|
|
||||||
# if grasshopper, will be nested under data: res["data"]
|
def process_diff(self, stream_id: str, commit_current: str, commit_previous: str):
|
||||||
# if rhino/autocad/revit, will be sent with layers or categories
|
"""
|
||||||
|
Process a diff operation between the specified
|
||||||
|
'current' commit and the 'previous' one.
|
||||||
|
"""
|
||||||
|
# Set the global variables
|
||||||
|
self.stream_id = stream_id
|
||||||
|
self.commit_current = commit_current
|
||||||
|
self.commit_prev = commit_previous
|
||||||
|
|
||||||
return res
|
print("Did not find existing diff, fetching commits now....")
|
||||||
|
# get meshes from commits
|
||||||
|
previous_commit = self.receive_data(
|
||||||
|
self.client, self.stream_id, self.commit_prev)
|
||||||
|
previous_meshes = self.get_all_meshes(previous_commit)
|
||||||
|
|
||||||
def get_all_meshes(child: Base):
|
current_commit = self.receive_data(
|
||||||
meshes = []
|
self.client, self.stream_id, self.commit_current)
|
||||||
|
current_meshes = self.get_all_meshes(current_commit)
|
||||||
|
|
||||||
names = child.get_dynamic_member_names()
|
print("Comparing meshes...")
|
||||||
for name in names:
|
diff_base = self.compare_meshes(current_meshes, previous_meshes)
|
||||||
prop = child[name]
|
|
||||||
if isinstance(prop, Base):
|
print("Diffing was successfull, sending to Speckle")
|
||||||
if isinstance(prop, Brep):
|
diff_commit_id = self.send_data(
|
||||||
if not hasattr(prop, "displayMesh"):
|
self.client,
|
||||||
|
self.stream_id,
|
||||||
|
self.diff_branch,
|
||||||
|
diff_base,
|
||||||
|
self.commit_current + "-" + self.commit_prev)
|
||||||
|
|
||||||
|
print("Successfully sent data to Speckle")
|
||||||
|
return self.client.commit.get(self.stream_id, diff_commit_id)
|
||||||
|
|
||||||
|
def check_existing_commits(self) -> bool or None:
|
||||||
|
"""Checks if a specific diff commit already exists in the diff_branch"""
|
||||||
|
branch_commits: Branch = self.client.branch.get(
|
||||||
|
self.stream_id, self.diff_branch, 50)
|
||||||
|
|
||||||
|
for commit in branch_commits.commits.items:
|
||||||
|
if commit.message == f"{self.commit_current}-{self.commit_prev}":
|
||||||
|
return commit
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def compare_meshes(self, current_meshes: List[Mesh], previous_meshes: List[Mesh]) -> Base:
|
||||||
|
"""
|
||||||
|
Compares the meshes from the first commit against the second, and sends the result to the `diff` branch.
|
||||||
|
It returns the commit url of the diff.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# pre process meshes in the current commit to check for same object ID (this means obj hasn't changed) - skip these
|
||||||
|
# if object id has changed, check for application id - if these are the same, compare these objects directly
|
||||||
|
matched_current_indices = []
|
||||||
|
matched_previous_indices = []
|
||||||
|
paired_current_indices = []
|
||||||
|
paired_previous_indices = []
|
||||||
|
for i in range(0, len(current_meshes), 1):
|
||||||
|
for j in range(0, len(previous_meshes), 1):
|
||||||
|
if current_meshes[i][1] == previous_meshes[j][1]:
|
||||||
|
matched_current_indices.append(i)
|
||||||
|
matched_previous_indices.append(j)
|
||||||
|
break
|
||||||
|
elif current_meshes[i][2] == previous_meshes[j][2]:
|
||||||
|
paired_current_indices.append(i)
|
||||||
|
paired_previous_indices.append(j)
|
||||||
break
|
break
|
||||||
meshes.append((prop.displayMesh, prop.id, prop.applicationId, prop))
|
|
||||||
elif isinstance(prop, Mesh):
|
|
||||||
meshes.append((prop, prop.id, prop.applicationId))
|
|
||||||
elif isinstance(prop, list):
|
|
||||||
for p in prop:
|
|
||||||
if isinstance(p, Brep):
|
|
||||||
if not hasattr(p, "displayMesh"):
|
|
||||||
break
|
|
||||||
meshes.append((p.displayMesh, p.id, p.applicationId, p))
|
|
||||||
elif isinstance(p, Mesh):
|
|
||||||
meshes.append((p, p.id, p.applicationId))
|
|
||||||
return meshes
|
|
||||||
|
|
||||||
|
# remove matched previous meshes and matched pairs and get list of all mesh points from processed list
|
||||||
def get_all_points(meshes: List[Mesh]):
|
# this will be used as reference for all meshes that have changed and don't have a specific match to compare to
|
||||||
points = []
|
previous_meshes_ref_pool = []
|
||||||
for mesh in meshes:
|
for i in range(0, len(previous_meshes), 1):
|
||||||
for i in range(2, len(mesh.vertices), 3):
|
if matched_previous_indices.__contains__(i) or paired_previous_indices.__contains__(i):
|
||||||
point = Point()
|
|
||||||
point.x = mesh.vertices[i-2]
|
|
||||||
point.y = mesh.vertices[i-1]
|
|
||||||
point.z = mesh.vertices[i]
|
|
||||||
points.append(point)
|
|
||||||
return points
|
|
||||||
|
|
||||||
|
|
||||||
def find_point(current: Point, points: List[Point]):
|
|
||||||
for point in points:
|
|
||||||
if (point.x == current.x and point.y == current.y and point.z == current.z):
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def find_closest_point(current: Point, points: List[Point]):
|
|
||||||
smallest_distance = None
|
|
||||||
for point in points:
|
|
||||||
d = ((current.x - point.x)**2 + (current.y - point.y)
|
|
||||||
** 2 + (current.z - point.z)**2)**0.5
|
|
||||||
if smallest_distance is not None:
|
|
||||||
if d > smallest_distance:
|
|
||||||
continue
|
continue
|
||||||
smallest_distance = d
|
previous_meshes_ref_pool.append(previous_meshes[i][0])
|
||||||
return smallest_distance
|
ref_pool = self.get_all_points(previous_meshes_ref_pool)
|
||||||
|
|
||||||
def check_existing_commits(
|
# create a ghosted render material
|
||||||
client: SpeckleClient, stream_id: str, commit_current: str, commit_previous: str
|
ghosted = RenderMaterial()
|
||||||
) -> Any:
|
ghosted.diffuse = WHITE
|
||||||
transport = ServerTransport(client, stream_id)
|
ghosted.opacity = 0.1
|
||||||
|
|
||||||
branch_commits: Branch = client.branch.get(stream_id, DIFF_BRANCH, 50)
|
# for each mesh in the current commit, compare mesh vertices with ref pool or matched pair to determine scale of change
|
||||||
|
diff_meshes = []
|
||||||
for commit in branch_commits.commits.items:
|
same_meshes = []
|
||||||
if commit.message == f"{commit_current}-{commit_previous}":
|
ref_meshes = []
|
||||||
return commit.id
|
diff_mesh_pairs = []
|
||||||
|
diff_mesh_ref_indices = [] # the corresponding ref pair mesh to diff mesh pairs
|
||||||
|
for i in range(0, len(current_meshes), 1):
|
||||||
|
mesh = current_meshes[i][0]
|
||||||
|
|
||||||
return None
|
# send matched current meshes with rendermaterial semi-transparent (ghosted)
|
||||||
|
if matched_current_indices.__contains__(i):
|
||||||
|
mesh.renderMaterial = ghosted
|
||||||
def compare_meshes(stream_id: str, commit_current: str, commit_previous: str):
|
same_meshes.append(mesh)
|
||||||
client = get_authenticated_client()
|
|
||||||
|
|
||||||
# see if existing diff commit already exists
|
|
||||||
# query for latest x commits in diff branch
|
|
||||||
# read commit message & parse
|
|
||||||
# return url if found
|
|
||||||
existing_commit = check_existing_commits(client, stream_id, commit_current, commit_previous)
|
|
||||||
if existing_commit is not None:
|
|
||||||
url = URL + stream_id + '/commits/' + existing_commit
|
|
||||||
return url
|
|
||||||
|
|
||||||
# get meshes from commits
|
|
||||||
previous_commit = receive_data(client, stream_id, commit_previous)
|
|
||||||
previous_meshes = get_all_meshes(previous_commit)
|
|
||||||
current_commit = receive_data(client, stream_id, commit_current)
|
|
||||||
current_meshes = get_all_meshes(current_commit)
|
|
||||||
|
|
||||||
# pre process meshes in the current commit to check for same object ID (this means obj hasn't changed) - skip these
|
|
||||||
# if object id has changed, check for application id - if these are the same, compare these objects directly
|
|
||||||
matched_current_indices = []
|
|
||||||
matched_previous_indices = []
|
|
||||||
paired_current_indices = []
|
|
||||||
paired_previous_indices =[]
|
|
||||||
for i in range(0, len(current_meshes), 1):
|
|
||||||
for j in range(0, len(previous_meshes), 1):
|
|
||||||
if current_meshes[i][1] == previous_meshes[j][1]:
|
|
||||||
matched_current_indices.append(i)
|
|
||||||
matched_previous_indices.append(j)
|
|
||||||
break
|
|
||||||
elif current_meshes[i][2] == previous_meshes[j][2]:
|
|
||||||
paired_current_indices.append(i)
|
|
||||||
paired_previous_indices.append(j)
|
|
||||||
break
|
|
||||||
|
|
||||||
# remove matched previous meshes and matched pairs and get list of all mesh points from processed list
|
|
||||||
# this will be used as reference for all meshes that have changed and don't have a specific match to compare to
|
|
||||||
previous_meshes_ref_pool = []
|
|
||||||
for i in range(0, len(previous_meshes), 1):
|
|
||||||
if matched_previous_indices.__contains__(i) or paired_previous_indices.__contains__(i):
|
|
||||||
continue
|
|
||||||
previous_meshes_ref_pool.append(previous_meshes[i][0])
|
|
||||||
ref_pool = get_all_points(previous_meshes_ref_pool)
|
|
||||||
|
|
||||||
# create a ghosted render material
|
|
||||||
ghosted = RenderMaterial()
|
|
||||||
ghosted.diffuse = WHITE
|
|
||||||
ghosted.opacity = 0.1
|
|
||||||
|
|
||||||
# for each mesh in the current commit, compare mesh vertices with ref pool or matched pair to determine scale of change
|
|
||||||
diff_meshes = []
|
|
||||||
same_meshes = []
|
|
||||||
ref_meshes = []
|
|
||||||
diff_mesh_pairs = []
|
|
||||||
diff_mesh_ref_indices = [] # the corresponding ref pair mesh to diff mesh pairs
|
|
||||||
for i in range(0, len(current_meshes), 1):
|
|
||||||
mesh = current_meshes[i][0]
|
|
||||||
|
|
||||||
# send matched current meshes with rendermaterial semi-transparent (ghosted)
|
|
||||||
if matched_current_indices.__contains__(i):
|
|
||||||
mesh.renderMaterial = ghosted
|
|
||||||
same_meshes.append(mesh)
|
|
||||||
continue
|
|
||||||
|
|
||||||
diff_mesh = mesh
|
|
||||||
vertices = get_all_points([mesh])
|
|
||||||
diff_mesh_colors = [WHITE] * (len(vertices))
|
|
||||||
diff_values = []
|
|
||||||
|
|
||||||
# check for pairing
|
|
||||||
paired_mesh_points = []
|
|
||||||
paired_ref_mesh_index = None
|
|
||||||
is_paired = False
|
|
||||||
if paired_current_indices.__contains__(i):
|
|
||||||
paired_ref_mesh_index = paired_previous_indices[paired_current_indices.index(i)]
|
|
||||||
paired_mesh_points = get_all_points([previous_meshes[paired_ref_mesh_index][0]])
|
|
||||||
is_paired = True
|
|
||||||
|
|
||||||
for vertex in vertices:
|
|
||||||
if is_paired:
|
|
||||||
diff_values.append(find_closest_point(vertex, paired_mesh_points))
|
|
||||||
else:
|
|
||||||
diff_values.append(find_closest_point(vertex, ref_pool))
|
|
||||||
|
|
||||||
# determine color value for vertex by remapping domain
|
|
||||||
changed = False
|
|
||||||
bin_size = max(diff_values) / len(COLORS)
|
|
||||||
for i in range(0, len(vertices), 1):
|
|
||||||
if diff_values[i] == 0:
|
|
||||||
continue
|
continue
|
||||||
else:
|
|
||||||
index = floor(diff_values[i] / bin_size)
|
diff_mesh = mesh
|
||||||
if index == len(COLORS):
|
vertices = self.get_all_points([mesh])
|
||||||
index -= 1
|
diff_mesh_colors = [WHITE] * (len(vertices))
|
||||||
diff_mesh_colors[i] = COLORS[index]
|
diff_values = []
|
||||||
changed = True
|
|
||||||
|
# check for pairing
|
||||||
|
paired_mesh_points = []
|
||||||
if not changed: # if hasn't changed, append to same list
|
paired_ref_mesh_index = None
|
||||||
|
is_paired = False
|
||||||
|
if paired_current_indices.__contains__(i):
|
||||||
|
paired_ref_mesh_index = paired_previous_indices[paired_current_indices.index(
|
||||||
|
i)]
|
||||||
|
paired_mesh_points = self.get_all_points(
|
||||||
|
[previous_meshes[paired_ref_mesh_index][0]])
|
||||||
|
is_paired = True
|
||||||
|
|
||||||
|
for vertex in vertices:
|
||||||
|
if is_paired:
|
||||||
|
diff_values.append(self.find_closest_point(
|
||||||
|
vertex, paired_mesh_points))
|
||||||
|
else:
|
||||||
|
diff_values.append(
|
||||||
|
self.find_closest_point(vertex, ref_pool))
|
||||||
|
|
||||||
|
# determine color value for vertex by remapping domain
|
||||||
|
changed = False
|
||||||
|
bin_size = max(diff_values) / len(COLORS)
|
||||||
|
for i in range(0, len(vertices), 1):
|
||||||
|
if diff_values[i] == 0:
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
index = math.floor(diff_values[i] / bin_size)
|
||||||
|
if index == len(COLORS):
|
||||||
|
index -= 1
|
||||||
|
diff_mesh_colors[i] = COLORS[index]
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if not changed: # if hasn't changed, append to same list
|
||||||
|
mesh.renderMaterial = ghosted
|
||||||
|
if is_paired:
|
||||||
|
matched_previous_indices.append(paired_ref_mesh_index)
|
||||||
|
same_meshes.append(mesh)
|
||||||
|
|
||||||
|
else: # set colors and add mesh to diff list or paired diff list
|
||||||
|
diff_mesh.colors = diff_mesh_colors
|
||||||
|
if is_paired:
|
||||||
|
diff_mesh_pairs.append(diff_mesh)
|
||||||
|
diff_mesh_ref_indices.append(paired_ref_mesh_index)
|
||||||
|
else:
|
||||||
|
diff_meshes.append(diff_mesh)
|
||||||
|
|
||||||
|
# process reference meshes
|
||||||
|
diff_mesh_refs = []
|
||||||
|
for j in range(0, len(previous_meshes)):
|
||||||
|
# skip matched reference meshes and paired refs
|
||||||
|
if matched_previous_indices.__contains__(j) or diff_mesh_ref_indices.__contains__(j):
|
||||||
|
continue
|
||||||
|
mesh = previous_meshes[j][0]
|
||||||
mesh.renderMaterial = ghosted
|
mesh.renderMaterial = ghosted
|
||||||
if is_paired:
|
ref_meshes.append(mesh)
|
||||||
matched_previous_indices.append(paired_ref_mesh_index)
|
for diff_mesh_ref_index in diff_mesh_ref_indices:
|
||||||
same_meshes.append(mesh)
|
mesh = previous_meshes[diff_mesh_ref_index]
|
||||||
|
|
||||||
else: # set colors and add mesh to diff list or paired diff list
|
|
||||||
diff_mesh.colors = diff_mesh_colors
|
|
||||||
if is_paired:
|
|
||||||
diff_mesh_pairs.append(diff_mesh)
|
|
||||||
diff_mesh_ref_indices.append(paired_ref_mesh_index)
|
|
||||||
else:
|
|
||||||
diff_meshes.append(diff_mesh)
|
|
||||||
|
|
||||||
# process reference meshes
|
|
||||||
diff_mesh_refs = []
|
|
||||||
for j in range(0, len(previous_meshes)):
|
|
||||||
if matched_previous_indices.__contains__(j) or diff_mesh_ref_indices.__contains__(j): # skip matched reference meshes and paired refs
|
|
||||||
continue
|
|
||||||
mesh = previous_meshes[j][0]
|
|
||||||
mesh.renderMaterial = ghosted
|
|
||||||
ref_meshes.append(mesh)
|
|
||||||
for diff_mesh_ref_index in diff_mesh_ref_indices:
|
|
||||||
mesh = previous_meshes[diff_mesh_ref_index]
|
|
||||||
if len(mesh) > 3:
|
|
||||||
diff_mesh_refs.append(mesh[3])
|
|
||||||
else:
|
|
||||||
diff_mesh_refs.append(mesh[0])
|
diff_mesh_refs.append(mesh[0])
|
||||||
|
|
||||||
# get units from first mesh in current commit
|
|
||||||
units = current_meshes[0][0].units
|
|
||||||
|
|
||||||
# create a new commit with the diff meshes
|
# Construct diff base object to return
|
||||||
return send_diff_data(stream_id, commit_current, commit_previous, units, diff_meshes, diff_mesh_pairs, diff_mesh_refs, same_meshes, ref_meshes)
|
base = Base()
|
||||||
|
base.units = current_meshes[0][0].units
|
||||||
|
base["changed"] = diff_meshes
|
||||||
|
for i in range(0, len(diff_mesh_pairs), 1):
|
||||||
|
layer = f"changed::{i}"
|
||||||
|
base[f"{layer}::changed"] = [diff_mesh_pairs[i]]
|
||||||
|
base[f"{layer}::ref"] = [diff_mesh_refs[i]]
|
||||||
|
base["same"] = same_meshes
|
||||||
|
base["ref"] = ref_meshes
|
||||||
|
return base
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def send_data(client: SpeckleClient, stream_id: str, branch: str, diff_object: Base, message: str) -> str:
|
||||||
|
"""Sends a Base object to a specified branch"""
|
||||||
|
# create a branch if necessary
|
||||||
|
branches = client.branch.list(stream_id)
|
||||||
|
has_res_branch = any(b.name == branch for b in branches)
|
||||||
|
|
||||||
def send_diff_data(stream_id: str, commit_current: str, commit_previous: str, units: str, changed: List[Mesh], changed_pairs: List[Mesh], ref_pairs: List[Base], unchanged: List[Mesh], ref: List[Mesh]):
|
if not has_res_branch:
|
||||||
client = get_authenticated_client()
|
client.branch.create(
|
||||||
|
stream_id, name=branch, description="This branch was created by the AEC Tech Masterclass App"
|
||||||
|
)
|
||||||
|
|
||||||
# create a branch if necessary
|
transport = ServerTransport(
|
||||||
branches = client.branch.list(stream_id)
|
client=client, stream_id=stream_id)
|
||||||
has_res_branch = any(b.name == DIFF_BRANCH for b in branches)
|
|
||||||
if not has_res_branch:
|
object_id = operations.send(base=diff_object, transports=[transport])
|
||||||
client.branch.create(
|
|
||||||
stream_id, name=DIFF_BRANCH, description="all your stream diff results"
|
commit_id = client.commit.create(
|
||||||
|
stream_id,
|
||||||
|
object_id, # object id
|
||||||
|
branch,
|
||||||
|
message
|
||||||
)
|
)
|
||||||
|
|
||||||
# create a commit with message "current_commit_id - previous_commit_id"
|
return commit_id
|
||||||
base = Base()
|
|
||||||
base.units = units
|
|
||||||
base["changed"] = changed
|
|
||||||
|
|
||||||
for i in range(0, len(changed_pairs), 1):
|
@staticmethod
|
||||||
layer = f"changed::{i}"
|
def receive_data(client: SpeckleClient, stream_id: str, commit_id: str) -> Base:
|
||||||
base[f"{layer}::changed"] = [changed_pairs[i]]
|
"""Get the data from a commit on the Speckle server"""
|
||||||
base[f"{layer}::ref"] = [ref_pairs[i]]
|
transport = ServerTransport(client, stream_id)
|
||||||
base["same"] = unchanged
|
|
||||||
base["ref"] = ref
|
|
||||||
|
|
||||||
transport = ServerTransport(client=client, stream_id=stream_id)
|
commit = client.commit.get(stream_id, commit_id)
|
||||||
|
res = operations.receive(commit.referencedObject, transport)
|
||||||
|
|
||||||
hash = operations.send(base=base, transports=[transport])
|
# if grasshopper, will be nested under data: res["data"]
|
||||||
|
# if rhino/autocad/revit, will be sent with layers or categories
|
||||||
|
|
||||||
commit_id = client.commit.create(
|
return res
|
||||||
stream_id,
|
|
||||||
hash, # object id
|
|
||||||
DIFF_BRANCH,
|
|
||||||
message=commit_current + "-" + commit_previous
|
|
||||||
)
|
|
||||||
|
|
||||||
return URL + stream_id + '/commits/' + commit_id
|
@staticmethod
|
||||||
|
def get_all_meshes(child: Base) -> List[Mesh]:
|
||||||
|
"""Returns all the meshes from a given Base object."""
|
||||||
|
meshes = []
|
||||||
|
|
||||||
# uncomment for debug
|
names = child.get_dynamic_member_names()
|
||||||
# compare_meshes(STREAM_ID, COMMIT_ID, PREV_COMMIT_ID)
|
for name in names:
|
||||||
|
prop = child[name]
|
||||||
|
if isinstance(prop, Base):
|
||||||
|
if isinstance(prop, Brep):
|
||||||
|
if not hasattr(prop, "displayMesh"):
|
||||||
|
break
|
||||||
|
meshes.append((prop.displayMesh, prop.id,
|
||||||
|
prop.applicationId, prop))
|
||||||
|
elif isinstance(prop, Mesh):
|
||||||
|
meshes.append((prop, prop.id, prop.applicationId))
|
||||||
|
elif isinstance(prop, list):
|
||||||
|
for p in prop:
|
||||||
|
if isinstance(p, Brep):
|
||||||
|
if not hasattr(p, "displayMesh"):
|
||||||
|
break
|
||||||
|
meshes.append(
|
||||||
|
(p.displayMesh, p.id, p.applicationId, p))
|
||||||
|
elif isinstance(p, Mesh):
|
||||||
|
meshes.append((p, p.id, p.applicationId))
|
||||||
|
elif hasattr(p, "displayMesh") or hasattr(p, "@displayMesh"):
|
||||||
|
meshes.append((p.displayMesh, p.id, p.applicationId))
|
||||||
|
|
||||||
|
return meshes
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_all_points(meshes: List[Mesh]) -> List[Point]:
|
||||||
|
"""Returns a flat list of vertices of all the meshes in a list"""
|
||||||
|
points = []
|
||||||
|
for mesh in meshes:
|
||||||
|
for i in range(2, len(mesh.vertices), 3):
|
||||||
|
point = Point()
|
||||||
|
point.x = mesh.vertices[i-2]
|
||||||
|
point.y = mesh.vertices[i-1]
|
||||||
|
point.z = mesh.vertices[i]
|
||||||
|
points.append(point)
|
||||||
|
return points
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def find_closest_point(current: Point, points: List[Point]):
|
||||||
|
"""Find the closest point to a target given a list of points"""
|
||||||
|
smallest_distance = None
|
||||||
|
for point in points:
|
||||||
|
d = ((current.x - point.x)**2 + (current.y - point.y)
|
||||||
|
** 2 + (current.z - point.z)**2)**0.5
|
||||||
|
if smallest_distance is not None:
|
||||||
|
if d > smallest_distance:
|
||||||
|
continue
|
||||||
|
smallest_distance = d
|
||||||
|
return smallest_distance
|
||||||
|
|||||||
@@ -0,0 +1,10 @@
|
|||||||
|
from mesh_diff import SpeckleMeshDiff
|
||||||
|
from specklepy.api.credentials import get_default_account
|
||||||
|
|
||||||
|
STREAM_ID = "8325294b8f"
|
||||||
|
COMMIT_ID = "c207299871" # current commit
|
||||||
|
PREV_COMMIT_ID = "b9f376d75d" # previous commit
|
||||||
|
|
||||||
|
account = get_default_account()
|
||||||
|
md = SpeckleMeshDiff(account.token)
|
||||||
|
md.process_diff(STREAM_ID, COMMIT_ID, PREV_COMMIT_ID)
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
// vetur.config.js
|
||||||
|
/** @type {import('vls').VeturConfig} */
|
||||||
|
module.exports = {
|
||||||
|
// **optional** default: `{}`
|
||||||
|
// override vscode settings
|
||||||
|
// Notice: It only affects the settings used by Vetur.
|
||||||
|
settings: {
|
||||||
|
"vetur.useWorkspaceDependencies": true,
|
||||||
|
"vetur.experimental.templateInterpolationService": true
|
||||||
|
},
|
||||||
|
// **optional** default: `[{ root: './' }]`
|
||||||
|
// support monorepos
|
||||||
|
projects: [
|
||||||
|
"./packages/frontend", // Shorthand for specifying only the project root location
|
||||||
|
{
|
||||||
|
// **required**
|
||||||
|
// Where is your project?
|
||||||
|
// It is relative to `vetur.config.js`.
|
||||||
|
root: "./packages/frontend",
|
||||||
|
// **optional** default: `'package.json'`
|
||||||
|
// Where is `package.json` in the project?
|
||||||
|
// We use it to determine the version of vue.
|
||||||
|
// It is relative to root property.
|
||||||
|
package: "./package.json",
|
||||||
|
|
||||||
|
// **optional** default: `[]`
|
||||||
|
// Register globally Vue component glob.
|
||||||
|
// If you set it, you can get completion by that components.
|
||||||
|
// It is relative to root property.
|
||||||
|
// Notice: It won't actually do it. You need to use `require.context` or `Vue.component`
|
||||||
|
globalComponents: ["./src/components/**/*.vue"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user