30 Commits

Author SHA1 Message Date
Alan Rynne 28aae5339e Merge remote-tracking branch 'origin/main' into main 2022-02-10 16:34:13 +01:00
Alan Rynne df0428c71c hotfix: Backend crash 2022-02-10 16:34:07 +01:00
Claire Kuang d16ac2ceae Update README.md 2021-11-04 20:13:41 +00:00
Claire Kuang 66f5f5c000 Update mesh_diff.py 2021-11-04 17:29:19 +00:00
Claire Kuang 6f6836ef51 Update README.md 2021-11-04 13:21:09 +00:00
Claire Kuang 54b812fa14 Update README.md 2021-11-04 13:20:38 +00:00
Alan Rynne f4bcb600bf Minor fix in server 2021-11-04 14:09:36 +01:00
Alan Rynne 6df659fbc5 Allow all origins 🤦🏼‍♂️ 2021-11-04 13:08:37 +01:00
Alan Rynne dd02f26d0b Hardcoded cors allowed origins 2021-11-04 13:04:15 +01:00
Alan Rynne c1e0155c18 hotfix: typo in main.py 2021-11-04 12:15:12 +01:00
Alan Rynne 8409552288 Added error logging 2021-11-04 12:11:47 +01:00
Alan Rynne e9fe4387cb Minor env var tweaks 2021-11-04 11:53:01 +01:00
Alan Rynne 0b4217465f Merge branch 'main' of https://github.com/specklesystems/speckle-aectech-masterclass into main 2021-11-04 11:39:58 +01:00
Alan Rynne 73ff2abb3c Moved reqs? 2021-11-04 11:39:56 +01:00
Alan Rynne 377a785a19 Moved reqs? 2021-11-04 11:39:05 +01:00
Alan Rynne 08e6694bc9 fix Procfile 2021-11-04 11:36:04 +01:00
Alan Rynne bf032646c4 Added deploy files 2021-11-04 11:24:43 +01:00
Alan Rynne b67ca7a487 Merge branch 'alan/frontend-backend-link' into main 2021-11-04 10:59:22 +01:00
Alan Rynne 208fdb4ae3 Minor cleanup and fixes 2021-11-04 10:59:15 +01:00
Alan Rynne 2d9f179c14 Mesh diff fix for revit elements :) 2021-11-03 18:29:56 +01:00
Alan Rynne 92a1788364 cleanup: Removed unused query 2021-11-03 16:56:41 +01:00
Alan Rynne faac4ee821 Initial readme file 2021-11-03 16:55:55 +01:00
Alan Rynne 164fc17edb Minor tweaks + vetur files 2021-11-03 16:55:55 +01:00
Claire Kuang 6962e82ccd minor cleanup 2021-11-03 09:26:20 +00:00
Alan Rynne 697906c5b5 UI tweaks + refactor of commit slider 2021-10-31 23:52:49 +01:00
Alan Rynne 0fb161f550 Frontend improvements 2021-10-31 21:00:40 +01:00
Alan Rynne c03e9d8262 Updated backend to return commit instead of url 2021-10-31 20:59:49 +01:00
Alan Rynne 34061fd12a Minor changes to mesh diff 2021-10-29 22:10:03 +02:00
Alan Rynne fcec358540 Mesh diff is now a full fledged python class 2021-10-29 18:35:13 +02:00
Alan Rynne 32eb5fb199 Isolated mesh_diff from test file plus simplified inputs in some functions 2021-10-29 17:54:15 +02:00
22 changed files with 781 additions and 558 deletions
+1
View File
@@ -0,0 +1 @@
web: uvicorn server.main:app --app-dir=server --host=0.0.0.0 --port=${PORT:-5000}
+124 -2
View File
@@ -1,2 +1,124 @@
# speckle-aectech-masterclass
web app for comparing Speckle stream commits
<h1 align="center">
<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&amp;style=flat-square&amp;logo=discourse&amp;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&amp;logo=read-the-docs&amp;logoColor=white" alt="docs"></a></p>
# About Speckle
What is Speckle? Check our ![YouTube Video Views](https://img.shields.io/youtube/views/B9humiSpHzM?label=Speckle%20in%201%20minute%20video&style=social)
### 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:
- [![speckle XYZ](https://img.shields.io/badge/https://-speckle.xyz-0069ff?style=flat-square&logo=hackthebox&logoColor=white)](https://speckle.xyz) ⇒ creating an account!
### Resources
- [![Community forum users](https://img.shields.io/badge/community-forum-green?style=for-the-badge&logo=discourse&logoColor=white)](https://speckle.community) for help, feature requests or just to hang with other speckle enthusiasts, check out our community forum!
- [![website](https://img.shields.io/badge/tutorials-speckle.systems-royalblue?style=for-the-badge&logo=youtube)](https://speckle.systems) our tutorials portal is full of resources to get you started using Speckle
- [![docs](https://img.shields.io/badge/docs-speckle.guide-orange?style=for-the-badge&logo=read-the-docs&logoColor=white)](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.
![diagram](https://user-images.githubusercontent.com/16748799/140319019-4ff437c6-21b6-4d13-a50f-c6b0f8eaa982.png)
## 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) API framework with a Uvicorn server implementation, 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!*
To deploy the server, open a command window in VSCode and navigate to your server folder. Run:
```shell
uvicorn main:app --reload
```
## 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)
To deploy the server, open a command window in VSCode and navigate to your frontend folder. Run:
```shell
npm install
npm run serve
```
## 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!
-78
View File
@@ -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&amp;style=flat-square&amp;logo=discourse&amp;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&amp;logo=read-the-docs&amp;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 ![YouTube Video Views](https://img.shields.io/youtube/views/B9humiSpHzM?label=Speckle%20in%201%20minute%20video&style=social)
### 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:
- [![speckle XYZ](https://img.shields.io/badge/https://-speckle.xyz-0069ff?style=flat-square&logo=hackthebox&logoColor=white)](https://speckle.xyz) ⇒ creating an account at
- [![create a droplet](https://img.shields.io/badge/Create%20a%20Droplet-0069ff?style=flat-square&logo=digitalocean&logoColor=white)](https://marketplace.digitalocean.com/apps/speckle-server?refcode=947a2b5d7dc1) ⇒ deploying an instance in 1 click
### Resources
- [![Community forum users](https://img.shields.io/badge/community-forum-green?style=for-the-badge&logo=discourse&logoColor=white)](https://speckle.community) for help, feature requests or just to hang with other speckle enthusiasts, check out our community forum!
- [![website](https://img.shields.io/badge/tutorials-speckle.systems-royalblue?style=for-the-badge&logo=youtube)](https://speckle.systems) our tutorials portal is full of resources to get you started using Speckle
- [![docs](https://img.shields.io/badge/docs-speckle.guide-orange?style=for-the-badge&logo=read-the-docs&logoColor=white)](https://speckle.guide/dev/) reference on almost any end-user and developer functionality
![Untitled](https://user-images.githubusercontent.com/2679513/132021739-15140299-624d-4410-98dc-b6ae6d9027ab.png)
# 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).
+3
View File
@@ -0,0 +1,3 @@
{
"include": ["./src/**/*"]
}
+7 -6
View File
@@ -1,5 +1,5 @@
{
"name": "speckle-revit-dashboard-app",
"name": "speckle-aec-tech-masterclass",
"version": "0.1.0",
"lockfileVersion": 1,
"requires": true,
@@ -11382,17 +11382,18 @@
}
},
"vue-eslint-parser": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.6.0.tgz",
"integrity": "sha512-QXxqH8ZevBrtiZMZK0LpwaMfevQi9UL7lY6Kcp+ogWHC88AuwUPwwCIzkOUc1LR4XsYAt/F9yHXAB/QoD17QXA==",
"version": "7.11.0",
"resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-7.11.0.tgz",
"integrity": "sha512-qh3VhDLeh773wjgNTl7ss0VejY9bMMa0GoDG2fQVyDzRFdiU3L7fw74tWZDHNQXdZqxO3EveQroa9ct39D2nqg==",
"dev": true,
"requires": {
"debug": "^4.1.1",
"eslint-scope": "^5.0.0",
"eslint-scope": "^5.1.1",
"eslint-visitor-keys": "^1.1.0",
"espree": "^6.2.1",
"esquery": "^1.4.0",
"lodash": "^4.17.15"
"lodash": "^4.17.21",
"semver": "^6.3.0"
},
"dependencies": {
"eslint-scope": {
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "speckle-revit-dashboard-app",
"name": "speckle-aec-tech-masterclass",
"version": "0.1.0",
"private": true,
"scripts": {
-10
View File
@@ -83,16 +83,6 @@
<v-icon small>mdi-account</v-icon>
</v-list-item-icon>
</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-title class="error--text">Log out</v-list-item-title>
<v-list-item-icon>
@@ -1,137 +1,135 @@
<template lang="html">
<v-sheet class="pa-4" elevation="8">
<p>Compare commit</p>
<v-row dense no-gutters>
<v-col cols="6">
<v-slide-group v-model="commitA" center-active 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 ? '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" }}
<div class="d-flex align-center">
<span style="white-space: nowrap;">
Compare commit
<v-chip
:close="currentCommit != null"
@click:close="$emit('update:currentCommit', null)"
@click="show_commit_selector = 'A'"
>
{{ currentCommit ? currentCommit.id : "Select commit" }}
</v-chip>
against
<v-chip :close="commitB != null" @click:close="commitB = null">
{{ commitB ? commitB.id : "Select commit" }}
<v-chip
:close="prevCommit != null"
@click:close="$emit('update:prevCommit', null)"
@click="show_commit_selector = 'B'"
>
{{ prevCommit ? prevCommit.id : "Select commit" }}
</v-chip>
</span>
<div class="pl-2">
<v-btn
v-if="!diffCommit"
color="success"
:disabled="!commitA || !commitB"
:disabled="!currentCommit || !prevCommit"
:loading="loading"
@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>
</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>
</template>
<script>
<script lang="js">
import { TOKEN } from "@/speckleUtils"
import CommitSlider from "@/components/commitSelector/CommitSlider.vue"
export default {
name: "CommitPanel",
components: {},
props: ["commits"],
components: { CommitSlider },
props: ["commits", "diffCommit", "showDiff", "currentCommit", "prevCommit"],
data() {
return {
commitA: null,
commitB: null
show_commit_selector: null,
loading: false,
}
},
methods: {
async requestDiff() {
console.log("diff requested for", this.commitA.id, this.commitB.id)
var diffUrl = `http://localhost:8000/diff/${this.$route.params.id}/${this.commitA.id}/${this.commitB.id}`
console.log(diffUrl)
fetch(diffUrl, {
toggleDiffView(){
this.$emit("update:showDiff", !this.showDiff)
},
async doesDiffExist(){
if(!this.currentCommit || !this.prevCommit) return { commit: null }
var backendUrl = process.env.VUE_APP_BACKEND_URL
var diffUrl = `${backendUrl}/diff_check/${this.$route.params.id}/${this.currentCommit.id}/${this.prevCommit.id}`
var res = await fetch(diffUrl, {
headers: {
method: "POST",
cors: "no-cors",
method: "GET",
Authorisation: `Bearer ${localStorage.getItem(TOKEN)}`,
"Content-type": "application/json",
"Access-Control-Allow-Origin": "*"
}
})
.then(async res => console.log("fetch success", await res.json(), res))
.catch(err => console.warn("fetch failed", err))
return await res.json()
},
async requestDiff() {
this.loading = true
var backendUrl = process.env.VUE_APP_BACKEND_URL
console.log("diff requested for", this.currentCommit.id, this.prevCommit.id)
var diffUrl = `${backendUrl}/diff/${this.$route.params.id}/${this.currentCommit.id}/${this.prevCommit.id}`
var res = await fetch(diffUrl, {
headers: {
method: "GET",
Authorisation: `Bearer ${localStorage.getItem(TOKEN)}`,
"Content-type": "application/json",
"Access-Control-Allow-Origin": "*"
}
})
if(res.status == 200){
var body = await res.json()
console.log("diff body", res, body)
this.$emit("update:diffCommit", body.commit)
}
this.loading = false
},
async handleCommitChange(event, value){
this.loading = true
this.$emit(event, value)
this.$emit("update:diff-commit", null)
if(value){
var diffRes = await this.doesDiffExist()
this.$emit("update:diff-commit", diffRes.commit)
}
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>
+10 -10
View File
@@ -221,8 +221,8 @@ export default {
type: Boolean,
default: false
},
objectUrls: {
type: Array,
objectUrl: {
type: String,
default: null
},
unloadTrigger: {
@@ -277,7 +277,9 @@ export default {
this.namedViews.push(...views)
}
},
objectUrls() {
objectUrl(newVal, oldVal) {
if (newVal == oldVal) return
console.log("obj url changed", newVal, oldVal)
this.unloadData()
this.load()
}
@@ -289,7 +291,6 @@ export default {
if (!this.viewer) {
this.viewer = new Viewer({ container: this.$refs.renderer })
}
this.viewer.onWindowResize()
this.setupEvents()
},
@@ -338,12 +339,11 @@ export default {
})
},
load() {
if (!this.objectUrls || this.objectUrls.length === 0) return
this.viewer.onWindowResize()
this.objectUrls?.forEach(url => {
this.viewer.loadObject(url, localStorage.getItem(TOKEN))
this.viewerLastLoadedUrl = url
})
if (!this.objectUrl) return
this.viewer.loadObject(this.objectUrl, localStorage.getItem(TOKEN))
this.viewerLastLoadedUrl = this.objectUrl
this.setupEvents()
this.hasLoadedModel = true
},
@@ -1,8 +0,0 @@
query {
user {
avatar
id
name
email
}
}
+4 -4
View File
@@ -14,15 +14,15 @@ const routes = [
component: Home,
meta: {
requiresAuth: true,
title: "Speckle Revit Dashboard",
title: "Speckle AEC Tech Masterclass",
metaTags: [
{
name: "description",
content: "The speckle Revit Dashboard homepage"
content: "The Speckle AEC Tech Masterclass homepage"
},
{
property: "og:description",
content: "The speckle Revit Dashboard homepage"
content: "The Speckle AEC Tech Masterclass homepage"
}
]
}
@@ -33,7 +33,7 @@ const routes = [
component: WelcomeView,
meta: {
requiresNoAuth: true,
title: "Login | Speckle Revit Dashboard"
title: "Login | Speckle AEC Tech Masterclass"
}
},
{
+15 -25
View File
@@ -1,5 +1,3 @@
export const userInfoQuery = `
query {
user {
@@ -20,17 +18,20 @@ export const streamCommitsQuery = `
name
updatedAt
id
commits(limit: $limit, cursor: $cursor) {
totalCount
cursor
items{
id
message
branchName
sourceApplication
referencedObject
authorName
createdAt
branch(name: "main"){
commits(limit: $limit, cursor: $cursor) {
totalCount
cursor
items{
id
message
branchName
sourceApplication
referencedObject
authorName
authorAvatar
createdAt
}
}
}
}
@@ -49,17 +50,6 @@ export const streamSearchQuery = `
}
}`
export const streamObjectQuery = `query($streamId: String!, $objectId: String!) {
stream(id: $streamId){
object(id: $objectId){
totalChildrenCount
id
speckleType
data
}
}
}`
export const latestStreamsQuery = `query {
streams(limit: 10){
cursor
@@ -72,4 +62,4 @@ export const latestStreamsQuery = `query {
updatedAt
}
}
}`
}`
-2
View File
@@ -88,8 +88,6 @@ export const searchStreams = (e) => speckleFetch(streamSearchQuery, {searchText:
// 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 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 getStreams = () => speckleFetch(latestStreamsQuery).then(res => res.data?.streams)
+21 -10
View File
@@ -1,16 +1,29 @@
<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>
<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-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-title>
<v-row class="pa-0 ma-0">
{{ stream.name }}
<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-list-item-title>
<v-list-item-subtitle class="caption primary--text">
@@ -25,11 +38,10 @@
</template>
<script>
import {getStreams, TOKEN} from "@/speckleUtils";
import { getStreams } from "@/speckleUtils"
export default {
name: 'Home',
name: "Home",
data() {
return {
streams: null
@@ -38,8 +50,7 @@ export default {
async mounted() {
this.streams = await getStreams()
},
methods: {
}
methods: {}
}
</script>
@@ -51,4 +62,4 @@ export default {
.v-data-footer__select {
display: none !important;
}
</style>
</style>
+32 -27
View File
@@ -1,20 +1,33 @@
<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">
<CommitPanel v-if="stream" :commits="stream.commits.items"></CommitPanel>
<CommitPanel
v-if="stream"
:showDiff.sync="showDiff"
:diffCommit.sync="diffCommit"
:currentCommit.sync="currentCommit"
:prevCommit.sync="prevCommit"
:commits="stream.branch.commits.items"
/>
</div>
<v-row class="fill-height" no-gutters>
<v-col fill-height cols="6">
<v-row class="fill-height" no-gutters v-show="!showDiff">
<v-col cols="6">
<Renderer
v-if="stream"
:object-urls="[objectUrl(0)]"
:object-url="objectUrl(currentCommit)"
show-selection-helper
></Renderer>
</v-col>
<v-col fill-height cols="6">
<v-col cols="6">
<Renderer
v-if="stream"
:object-urls="[objectUrl(1)]"
:object-url="objectUrl(prevCommit)"
show-selection-helper
></Renderer>
</v-col>
</v-row>
<v-row class="fill-height" no-gutters>
<v-col fill-height :cols="12">
<Renderer
:object-url="objectUrl(diffCommit)"
show-selection-helper
></Renderer>
</v-col>
@@ -23,7 +36,7 @@
</template>
<script>
import { getStreamCommits, getStreamObject } from "@/speckleUtils"
import { getStreamCommits } from "@/speckleUtils"
import Renderer from "../components/viewer/Renderer.vue"
import CommitPanel from "@/components/commitSelector/CommitPanel.vue"
@@ -33,11 +46,12 @@ export default {
data() {
return {
stream: null,
selectedCommit: null,
refObj: null,
currentCommit: null,
prevCommit: null,
diffCommit: null,
serverUrl: process.env.VUE_APP_SERVER_URL,
loading: true,
progress: 0
showDiff: false,
loading: true
}
},
async mounted() {
@@ -46,6 +60,7 @@ export default {
}
},
computed: {
/** @return {string} */
streamId() {
return this.$route.params.id
}
@@ -53,13 +68,11 @@ export default {
methods: {
async getStream() {
var res = await getStreamCommits(this.streamId, 10, null)
this.selectedCommit = res.data.stream.commits.items[0]
this.stream = res.data.stream
},
objectUrl(i) {
return [
`${this.serverUrl}/streams/${this.stream.id}/objects/${this.stream.commits.items[i].referencedObject}`
]
objectUrl(commit) {
if (!commit) return null
return `${this.serverUrl}/streams/${this.stream.id}/objects/${commit.referencedObject}`
}
},
watch: {
@@ -67,14 +80,6 @@ export default {
handler: async function(val, oldVal) {
if (val) this.getStream()
}
},
selectedCommit: {
handler: async function() {
this.refObj = await getStreamObject(
this.stream.id,
this.selectedCommit.referencedObject
)
}
}
}
}
+8 -5
View File
@@ -1,16 +1,19 @@
<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>
<h1>Welcome to the Speckle Revit Dashboard</h1>
<p>This app allows you to analyse the data sent from Revit to Speckle.</p>
<h1>Welcome to the Speckle Diff Visualizer</h1>
<p>This app generates a heat map diff of changed geometry.</p>
<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>
<p class="grey--text">Please log in to access you Speckle data.</p>
</v-container>
</template>
<script>
export default {
name: 'WelcomeView'
name: "WelcomeView"
}
</script>
+6
View File
@@ -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
View File
@@ -1,33 +1,74 @@
from typing import Optional
from fastapi import FastAPI, Request
from mesh_diff import compare_meshes
"""FastAPI Backend for the AEC Tech Masterclass"""
import os
from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from mesh_diff import SpeckleMeshDiff
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 = [
"http://localhost",
"http://localhost:8080",
"https://speckle-aectech-masterclass.netlify.app",
"http://speckle-aectech-masterclass.netlify.app"
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
def read_root():
return {"Hello": "World"}
@app.get("/diff/{stream_id}/{commit_current}/{commit_previous}")
def get_diff(stream_id: str, commit_current: str, commit_previous: str, request: Request):
print(request)
print(request.headers.get("Authorisation"))
token = request.headers.get("Authorisation").split(" ")[1]
print(token)
return compare_meshes(stream_id, commit_current, commit_previous, token)
"""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)
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))
+261 -250
View File
@@ -1,290 +1,301 @@
from array import array
from hashlib import new
from logging import NullHandler
from types import AsyncGeneratorType
from typing import Any, List
from math import floor
"""SpeckleMeshDiff for AEC Tech Masterclass"""
from typing import List
import math
from specklepy.api import operations
from specklepy.api.client import SpeckleClient
from specklepy.api.credentials import get_default_account
from specklepy.api.models import Branch
from specklepy.api.resources import stream
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.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, -
32640, -39322, -45747, -52429, -59111, -65536]
COLORS = [-6426, -13108, -19790, -26215, -32640, -39322, -45747, -52429, -59111, -65536]
WHITE = -1
def get_authenticated_client(token: str) -> SpeckleClient:
client = SpeckleClient(host=HOST)
client.authenticate(token=token)
return client
class SpeckleMeshDiff:
"""Class to handle diffing between commits in a stream."""
client: SpeckleClient = None
host: str = None
diff_branch: str = None
def receive_data(
client: SpeckleClient, stream_id: str, commit_id: str
) -> Any:
transport = ServerTransport(client, stream_id)
commit_prev: str = None
commit_current: str = None
stream_id: str = None
commit = client.commit.get(stream_id, commit_id)
res = operations.receive(commit.referencedObject, transport)
def __init__(self, token: str, host: str = "https://speckle.xyz", diff_branch: str = "diff"):
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"]
# if rhino/autocad/revit, will be sent with layers or categories
def process_diff(self, stream_id: str, commit_current: str, commit_previous: str):
"""
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):
meshes = []
current_commit = self.receive_data(
self.client, self.stream_id, self.commit_current)
current_meshes = self.get_all_meshes(current_commit)
names = child.get_dynamic_member_names()
for name in names:
prop = child[name]
if isinstance(prop, Base):
if isinstance(prop, Brep):
if not hasattr(prop, "displayMesh"):
print("Comparing meshes...")
diff_base = self.compare_meshes(current_meshes, previous_meshes)
print("Diffing was successfull, sending to Speckle")
diff_commit_id = self.send_data(
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)
if(branch_commits is None):
return None
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
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
def get_all_points(meshes: List[Mesh]):
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
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:
# 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
smallest_distance = d
return smallest_distance
previous_meshes_ref_pool.append(previous_meshes[i][0])
ref_pool = self.get_all_points(previous_meshes_ref_pool)
def check_existing_commits(
client: SpeckleClient, stream_id: str, commit_current: str, commit_previous: str
) -> Any:
transport = ServerTransport(client, stream_id)
# create a ghosted render material
ghosted = RenderMaterial()
ghosted.diffuse = WHITE
ghosted.opacity = 0.1
branch_commits: Branch = client.branch.get(stream_id, DIFF_BRANCH, 50)
for commit in branch_commits.commits.items:
if commit.message == f"{commit_current}-{commit_previous}":
return commit.id
# 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]
return None
def compare_meshes(stream_id: str, commit_current: str, commit_previous: str):
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:
# send matched current meshes with rendermaterial semi-transparent (ghosted)
if matched_current_indices.__contains__(i):
mesh.renderMaterial = ghosted
same_meshes.append(mesh)
continue
else:
index = 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
diff_mesh = mesh
vertices = self.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 = 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
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)):
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:
ref_meshes.append(mesh)
for diff_mesh_ref_index in diff_mesh_ref_indices:
mesh = previous_meshes[diff_mesh_ref_index]
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
return send_diff_data(stream_id, commit_current, commit_previous, units, diff_meshes, diff_mesh_pairs, diff_mesh_refs, same_meshes, ref_meshes)
# Construct diff base object to return
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]):
client = get_authenticated_client()
if not has_res_branch:
client.branch.create(
stream_id, name=branch, description="This branch was created by the AEC Tech Masterclass App"
)
# create a branch if necessary
branches = client.branch.list(stream_id)
has_res_branch = any(b.name == DIFF_BRANCH for b in branches)
if not has_res_branch:
client.branch.create(
stream_id, name=DIFF_BRANCH, description="all your stream diff results"
transport = ServerTransport(
client=client, stream_id=stream_id)
object_id = operations.send(base=diff_object, transports=[transport])
commit_id = client.commit.create(
stream_id,
object_id, # object id
branch,
message
)
# create a commit with message "current_commit_id - previous_commit_id"
base = Base()
base.units = units
base["changed"] = changed
return commit_id
for i in range(0, len(changed_pairs), 1):
layer = f"changed::{i}"
base[f"{layer}::changed"] = [changed_pairs[i]]
base[f"{layer}::ref"] = [ref_pairs[i]]
base["same"] = unchanged
base["ref"] = ref
@staticmethod
def receive_data(client: SpeckleClient, stream_id: str, commit_id: str) -> Base:
"""Get the data from a commit on the Speckle server"""
transport = ServerTransport(stream_id, client)
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(
stream_id,
hash, # object id
DIFF_BRANCH,
message=commit_current + "-" + commit_previous
)
return res
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
# compare_meshes(STREAM_ID, COMMIT_ID, PREV_COMMIT_ID)
names = child.get_dynamic_member_names()
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
+10
View File
@@ -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)
+34
View File
@@ -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"]
}
]
}