Compare commits

..

10 Commits

Author SHA1 Message Date
Alan Rynne 6e92c857a7 Merge pull request #84 from specklesystems/dev
Update `main` with changes from `dev`
2024-11-05 11:53:21 +01:00
Alan Rynne 8edc01b7d6 fix: Update viewer to version 2.21.0 (#82) 2024-11-05 11:26:09 +01:00
Claire Kuang 6d5f638895 Merge pull request #83 from specklesystems/claire/cnx-699-update-github-links-to-point-to-v3
Update README.md to align with main github page
2024-11-01 18:51:47 +00:00
Claire Kuang ccecf7cb2b Update README.md to align with main github page 2024-11-01 18:49:03 +00:00
Mucahit Bilal GOKER 156c3a5c3a return model id instead of name (#81) 2024-10-01 18:02:00 +02:00
Alan Rynne 887dbb2344 Merge branch 'main' into dev 2024-09-09 16:19:13 +02:00
Mucahit Bilal GOKER 556196a45f fixed extension path (#79) 2024-09-05 15:38:02 +02:00
Jedd Morgan 1bf6e76252 Merge pull request #78 from specklesystems/dev
Merge dev -> main
2024-08-08 14:41:41 +01:00
Mucahit Bilal GOKER b26801ef8a Update README.md (#76) 2024-08-08 14:40:35 +01:00
Iain Sproat 74ab91be3b chore(domains): update speckle.xyz to app.speckle.systems (#77)
- remove reference to DO 1-click as this is deprecated
2024-08-08 14:39:07 +01:00
15 changed files with 336 additions and 227 deletions
+68 -62
View File
@@ -1,14 +1,74 @@
<h1 align="center">
<<h1 align="center">
<img src="https://user-images.githubusercontent.com/2679513/131189167-18ea5fe1-c578-47f6-9785-3748178e4312.png" width="150px"/><br/>
Speckle | PowerBI
Speckle | Power BI
</h1>
<h3 align="center">
Data Connector and 3D Viewer Visual for PowerBI platform
</h3>
<p align="center"><b>Speckle</b> is the 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"></p>
> Speckle is the first AEC data hub that connects with your favorite AEC tools. Speckle exists to overcome the challenges of working in a fragmented industry where communication, creative workflows, and the exchange of data are often hindered by siloed software and processes. It is here to make the industry better.
<h3 align="center">
Speckle Connector and 3D Viewer Visual for Power BI
</h3>
# Features
Speckle Power BI Data Connector lets you easily get data from Speckle into Power BI reports and visualizations. You can access and analyze data from various AEC apps (like Revit, Archicad, Grasshopper, and more) and open-source files (IFC, STL, OBJ, etc.) into Power BI with ease.
Speckles connection to Power BI consists of two parts:
- **Data Connector** fetches the data you uploaded from AEC apps to Speckle.
- **3D Visual** allows you to see those models in 3D within Power BI.
![Desktop - 1 (1)](https://github.com/specklesystems/speckle-powerbi/assets/51519350/6d2c5224-965f-4eae-b869-be26cb48c6b2)
# Repo Structure
This repo is home to our Power BI connector. The Speckle Server provides all the web-facing functionality and can be found [here](https://github.com/specklesystems/Server).
`src/powerbi-data-connector` contains all the code for the Data connector.
`src/powerbi-visual` contains all the code for 3D Visual.
# Installation
Speckle connector can be installed directly from [Manager for Speckle](https://speckle.systems/download/). Full instructions for [installation](https://speckle.guide/user/powerbi/installation.html) and [configuration](https://speckle.guide/user/powerbi/configuration.html) can be found on our docs.
# Using 3D Visual
3D Visual can be imported as any other Power BI custom visual.
1. Navigate to the Visualization Pane.
2. Click the three dots (…) and select “Import a visual from a file”.
3. Go to `Documents/Power BI Desktop/Custom Visuals` and import `Speckle 3D Visual.pbiviz` file.
4. Speckle cube will appear in the Visualization pane.
For more on how to use the visual, [check our docs](https://speckle.guide/user/powerbi-visual/introduction.html).
# Usage
To get started with Power BI connectors, please take a look at the [documentation](https://speckle.guide/user/powerbi/introduction.html) and extensive [tutorials](https://www.youtube.com/playlist?list=PLlI5Dyt2HaEsZHG2WJ75WIM0Brx6VHT2S) published.
# **Developing & Debugging**
We encourage everyone interested to debug/hack/contribute/give feedback to this project.
## **Setup**
### **Install PowerQuery SDK**
Follow the instructions from the [official docs](https://docs.microsoft.com/en-us/power-query/installingsdk)
### **Build with Visual Studio**
Every time you build the connector, VisualStudio will copy the latest `.mez` connector file to the appropriate location. Just restart PowerBI to see the latest changes.
### **Debug**
You can start the PowerQuery connector in VisualStudio, this will open a standalone connector you can use for testing purposes.
We don't know of a way to debug the connector live in PowerBI, but we'd be happy to hear about it.
# About Speckle
@@ -31,8 +91,7 @@ What is Speckle? Check our ![YouTube Video Views](https://img.shields.io/youtube
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 our public server
- [![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
- [![app.speckle.systems](https://img.shields.io/badge/https://-speckle.xyz-0069ff?style=flat-square&logo=hackthebox&logoColor=white)](https://app.speckle.systems) ⇒ creating an account at our public server
### Resources
@@ -41,56 +100,3 @@ Give Speckle a try in no time by:
- [![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
This repo is the home to our Speckle 2.0 PowerBI project. The [Speckle Server](https://github.com/specklesystems/Server) is providing all the web-facing functionality and can be found [here](https://github.com/specklesystems/Server).
## Install
Go to the [Releases](https://github.com/specklesystems/speckle-powerbi/releases) page, downlad the `.mez` file of the latest release and copy it into the following folder in your computer:
```
YOUR_USER_FOLDER\Documents\Power BI Desktop\Custom Connectors\
```
If the folder doesn't exist, create it.
### Allow custom extensions to run
Go to `Settings -> Security -> Data Extensions` and activate the following option:
![Allow extensions to run](https://user-images.githubusercontent.com/2316535/130931149-074cf6a8-1910-41f1-99c7-b8b08168f473.png)
### Checking the connector is loaded
Now open PowerBI and you should see `Speckle (beta)` appear in the data source.
![PowerBI](https://user-images.githubusercontent.com/2316535/129580913-02e5e662-f344-419c-9894-e97055930c58.png)
## Usage
> More detailed instructions on how to use the connector will be added shortly!
### Current limitations
Chunked data currently is not automatically de-chunked when received, we are aware of this limitation and are working to resolve it!
## Developing & Debugging
We encourage everyone interested to debug / hack / contribute / give feedback to this project.
### Setup
#### Install PowerQuery SDK
Follow the instructions from the [official docs](https://docs.microsoft.com/en-us/power-query/installingsdk)
#### Build with Visual Studio
Every time you build the connector, VisualStudio will copy the latest `.mez` connector file to the appropriate location. Just restart PowerBI to see the latest changes.
#### Debug
You can start the PowerQuery connector in VisualStudio, this will open a standalone connector you can use for testing purposes.
We don't know of a way to debug the connector live in PowerBI, but we'd be happy to hear about it.
+6 -1
View File
@@ -1,2 +1,7 @@
// Use this file to write queries to test your data connector
let result = Speckle.GetByUrl("https://app.speckle.systems/projects/e2988234fb/models/60b2300470@b1f31a351a") in result
let
result = Speckle.GetByUrl(
"https://app.speckle.systems/projects/e2988234fb/models/60b2300470@b1f31a351a,60b2300470"
)
in
result
@@ -44,13 +44,15 @@ in
addSpeckleTypeCol = Table.AddColumn(
addObjectIdCol, "speckle_type", each try[data][speckle_type] otherwise null
),
// TODO: JSON Column must be added here so that any detached objects can be re-attached before doing the json thing. If we just pick the raw result from the API, it will only work in the simplest of objects.
addJsonCol = Table.AddColumn(
addSpeckleTypeCol, "json", each try Text.FromBinary(Json.FromValue([data])) otherwise null
),
final = Table.ReorderColumns(
addJsonCol,
{"Model URL", "URL Type", "Version Object ID", "Object ID", "speckle_type", "data", "json"}
addSpeckleTypeCol, {
"Model URL",
"URL Type",
"Version Object ID",
"Object ID",
"speckle_type",
"data"
}
)
in
final
@@ -63,7 +63,7 @@ let
urlType = urlType,
server = server,
id = streamId,
branch = model[name],
branch = modelId,
commit = versionId,
object = null
]
@@ -1,2 +1,2 @@
// Use this file to write queries to test your data connector
let result = Speckle.Api.Fetch("https://latest.speckle.dev") in Record.ToTable(result)
let result = Speckle.Api.Fetch("https://latest.speckle.systems") in Record.ToTable(result)
@@ -1,7 +1,7 @@
// Use this file to write queries to test your data connector
let
result = Speckle.Api.REST.GetObject(
"https://latest.speckle.dev", "5f284e5c70", "85e5f250fe591ea74d8d5dc1137a9341"
"https://latest.speckle.systems", "5f284e5c70", "85e5f250fe591ea74d8d5dc1137a9341"
)
in
result
@@ -1,2 +1,2 @@
// Use this file to write queries to test your data connector
let result = Speckle.Get.ByUrl("https://latest.speckle.dev/streams/3d25474a18") in Record.ToTable(result)
let result = Speckle.Get.ByUrl("https://latest.speckle.systems/streams/3d25474a18") in Record.ToTable(result)
@@ -1,2 +1,2 @@
// Use this file to write queries to test your data connector
let result = Speckle.GetByUrl("https://latest.speckle.dev/streams/5f284e5c70/objects/85e5f250fe591ea74d8d5dc1137a9341") in result
let result = Speckle.GetByUrl("https://latest.speckle.systems/streams/5f284e5c70/objects/85e5f250fe591ea74d8d5dc1137a9341") in result
+12 -11
View File
@@ -20,11 +20,6 @@
"kind": "Grouping",
"name": "objectColorBy"
},
{
"displayName": "JSON Data",
"kind": "Measure",
"name": "json"
},
{
"displayName": "Tooltip Data",
"kind": "Measure",
@@ -41,6 +36,16 @@
}
},
"select": [
{
"bind": {
"to": "stream"
}
},
{
"bind": {
"to": "parentObject"
}
},
{
"bind": {
"to": "objectColorBy"
@@ -55,11 +60,6 @@
},
"values": {
"select": [
{
"bind": {
"to": "json"
}
},
{
"bind": {
"to": "objectData"
@@ -206,9 +206,10 @@
"essential": true,
"name": "WebAccess",
"parameters": [
"https://*.speckle.systems",
"https://app.speckle.systems",
"https://speckle.xyz",
"https://*.speckle.xyz",
"https://latest.speckle.systems",
"https://latest.speckle.dev",
"https://*.speckle.dev",
"https://analytics.speckle.systems",
+111 -30
View File
@@ -15,7 +15,7 @@
"@heroicons/vue": "^2.0.12",
"@speckle/tailwind-theme": "2.14.7",
"@speckle/ui-components": "2.14.7",
"@speckle/viewer": "^2.18.14",
"@speckle/viewer": "^2.21.0",
"color-interpolate": "^1.0.5",
"core-js": "^3.30.2",
"lodash": "^4.17.21",
@@ -2659,13 +2659,15 @@
}
},
"node_modules/@speckle/objectloader": {
"version": "2.18.16",
"resolved": "https://registry.npmjs.org/@speckle/objectloader/-/objectloader-2.18.16.tgz",
"integrity": "sha512-YykdjxVkYlND+tgjHUL2I+Djaggm+w2v+UKZSIDSt7LVGIDg88JXJZ/JbO9B8ptx16i0E2oCQRuiTCntimoqZQ==",
"version": "2.21.0",
"resolved": "https://registry.npmjs.org/@speckle/objectloader/-/objectloader-2.21.0.tgz",
"integrity": "sha512-kGsQ1UKDR8sYE2m2b5poVocH3bbmRWmvPPG/ST7td0QjsYBKWRFJAX8fDmT6PSwbtN8reA9wKRtMJoO2OJvjBA==",
"dependencies": {
"@babel/core": "^7.17.9",
"@speckle/shared": "^2.18.16",
"@speckle/shared": "^2.21.0",
"core-js": "^3.21.1",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"regenerator-runtime": "^0.13.7"
},
"engines": {
@@ -2673,11 +2675,11 @@
}
},
"node_modules/@speckle/shared": {
"version": "2.18.16",
"resolved": "https://registry.npmjs.org/@speckle/shared/-/shared-2.18.16.tgz",
"integrity": "sha512-I5/jmvbuOjDJYjFY04f3IsDnQ0/Rt6DQKekXWiuyfmuLUqsBvM9hbWZBsg3vPmvX2HFgfQxisZAD0bHwpNTb3A==",
"version": "2.21.0",
"resolved": "https://registry.npmjs.org/@speckle/shared/-/shared-2.21.0.tgz",
"integrity": "sha512-g9DsCeliDwQsgHLrYXKpWSaFMoTQ5yOraiEUzdQFMZGmM9IZNRztq4/74YLS+Wy5MutT79KFYsOwng60IcaB/w==",
"dependencies": {
"lodash": "^4.17.0",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"type-fest": "^3.11.1"
},
@@ -2686,9 +2688,13 @@
},
"peerDependencies": {
"@tiptap/core": "^2.0.0-beta.176",
"mixpanel": "^0.17.0",
"pino": "^8.7.0",
"pino-http": "^8.0.0",
"pino-pretty": ">=8.0.0"
"pino-pretty": ">=8.0.0",
"ua-parser-js": "^1.0.38",
"znv": "^0.4.0",
"zod": "^3.22.4"
}
},
"node_modules/@speckle/tailwind-theme": {
@@ -2727,15 +2733,14 @@
}
},
"node_modules/@speckle/viewer": {
"version": "2.18.16",
"resolved": "https://registry.npmjs.org/@speckle/viewer/-/viewer-2.18.16.tgz",
"integrity": "sha512-exIloJG6ZLDX5FgrxyTcbr1+7K3CXC8weKJkWc6d1nZITGiDZnPB6CG3ffEmudKEdbPirUpGuYX5QsqZvvcA5w==",
"version": "2.21.0",
"resolved": "https://registry.npmjs.org/@speckle/viewer/-/viewer-2.21.0.tgz",
"integrity": "sha512-fok7WW0RTcJpe4HsRQd1wqq+ill+3o1eIAfAnHJuzLqMqqnO7e64bNE18b42fHJWOgC3FzQCmh/xPChpNU7w7w==",
"dependencies": {
"@speckle/objectloader": "^2.18.16",
"@speckle/shared": "^2.18.16",
"@speckle/objectloader": "^2.21.0",
"@speckle/shared": "^2.21.0",
"@types/flat": "^5.0.2",
"camera-controls": "^1.33.1",
"hold-event": "^0.1.0",
"flat": "^5.0.2",
"js-logger": "1.6.1",
"lodash-es": "^4.17.21",
"string-to-color": "^2.2.2",
@@ -3836,6 +3841,18 @@
"node": ">=0.4.0"
}
},
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"peer": true,
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
@@ -4514,14 +4531,6 @@
"node": ">= 6"
}
},
"node_modules/camera-controls": {
"version": "1.38.2",
"resolved": "https://registry.npmjs.org/camera-controls/-/camera-controls-1.38.2.tgz",
"integrity": "sha512-EfzbovxLssyWpJVG9uKcazSDDIEcd1hUsPhPF/OWWnICsKY9WbLY/2S4UPW73HHbvnVeR/Z9wsWaQKtANy/2Yg==",
"peerDependencies": {
"three": ">=0.126.1"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001625",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001625.tgz",
@@ -6267,6 +6276,14 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/flat": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
"integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
"bin": {
"flat": "cli.js"
}
},
"node_modules/flat-cache": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
@@ -6702,11 +6719,6 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
"node_modules/hold-event": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/hold-event/-/hold-event-0.1.0.tgz",
"integrity": "sha512-k4RvgnpTn8ZIPmFoHYnSRfyhfUTV+L2K5ruuIHT2IdgJqvNc6BCHGd/fSck9imqxW25xsxONZo9NN1DPlnbnLg=="
},
"node_modules/hpack.js": {
"version": "2.1.6",
"resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz",
@@ -6829,6 +6841,19 @@
"integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==",
"dev": true
},
"node_modules/https-proxy-agent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
"integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==",
"peer": true,
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/human-signals": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
@@ -7935,6 +7960,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/mixpanel": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/mixpanel/-/mixpanel-0.17.0.tgz",
"integrity": "sha512-DY5WeOy/hmkPrNiiZugJpWR0iMuOwuj1a3u0bgwB2eUFRV6oIew/pIahhpawdbNjb+Bye4a8ID3gefeNPvL81g==",
"peer": true,
"dependencies": {
"https-proxy-agent": "5.0.0"
},
"engines": {
"node": ">=10.0"
}
},
"node_modules/mrmime": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.0.tgz",
@@ -12047,6 +12084,29 @@
"node": ">=12.20"
}
},
"node_modules/ua-parser-js": {
"version": "1.0.38",
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.38.tgz",
"integrity": "sha512-Aq5ppTOfvrCMgAPneW1HfWj66Xi7XL+/mIy996R1/CLS/rcyJQm6QZdsKrUeivDFQ+Oc9Wyuwor8Ze8peEoUoQ==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/ua-parser-js"
},
{
"type": "paypal",
"url": "https://paypal.me/faisalman"
},
{
"type": "github",
"url": "https://github.com/sponsors/faisalman"
}
],
"peer": true,
"engines": {
"node": "*"
}
},
"node_modules/uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",
@@ -12963,6 +13023,27 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/znv": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/znv/-/znv-0.4.0.tgz",
"integrity": "sha512-6/pGsQhBisLzKdyC90mUCRgYDtCfQ4aQ68sDybexq3GMzqqkp662GH6qIyuCHJC1i72hJPHbWAhccTJVuZUQfA==",
"peer": true,
"dependencies": {
"colorette": "^2.0.19"
},
"peerDependencies": {
"zod": "^3.13.2"
}
},
"node_modules/zod": {
"version": "3.23.8",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
"peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}
+1 -1
View File
@@ -19,7 +19,7 @@
"@heroicons/vue": "^2.0.12",
"@speckle/tailwind-theme": "2.14.7",
"@speckle/ui-components": "2.14.7",
"@speckle/viewer": "^2.18.14",
"@speckle/viewer": "^2.21.0",
"color-interpolate": "^1.0.5",
"core-js": "^3.30.2",
"lodash": "^4.17.21",
@@ -6,7 +6,8 @@ import {
DefaultViewerParams,
Box3,
SpeckleView,
CameraController
CameraController,
CameraEvent
} from '@speckle/viewer'
import { pickViewableHit, projectToScreen } from '../utils/viewerUtils'
import _ from 'lodash'
@@ -38,10 +39,9 @@ export default class ViewerHandler {
break
}
this.viewer.getExtension(CameraController).controls.maxPolarAngle = settings.camera
.allowCameraUnder.value
? Math.PI
: Math.PI / 2
var camController = this.viewer.getExtension(CameraController)
var angle = settings.camera.allowCameraUnder.value ? Math.PI : Math.PI / 2
camController.options = { maximumPolarAngle: angle }
// Lighting settings
const newConfig = settings.lighting.getViewerConfiguration()
@@ -72,7 +72,7 @@ export default class ViewerHandler {
}
public addCameraUpdateEventListener(listener: (ev) => void) {
this.viewer.getExtension(CameraController).controls.addEventListener('update', listener)
this.viewer.getExtension(CameraController).on(CameraEvent.LateFrameUpdate, listener)
}
public constructor(parent: HTMLElement) {
@@ -90,74 +90,62 @@ export default class ViewerHandler {
}
public async unloadObjects(
objectIds: string[],
objects: string[],
signal?: AbortSignal,
onObjectUnloaded?: (url: string) => void
) {
console.log('Unloading objects', objectIds)
for (const speckleObjectId of objectIds) {
for (const url of objects) {
if (signal?.aborted) return
// TODO: Here's where the viewer unloads any objects that have been removed. It first ensures any loading is cancelled.
// await this.viewer
// .cancelLoad(url, true)
// .catch((e) => console.warn('Viewer error while cancelling load', url, e))
if (this.loadedObjectsCache.has(speckleObjectId))
this.loadedObjectsCache.delete(speckleObjectId)
if (onObjectUnloaded) onObjectUnloaded(speckleObjectId)
await this.viewer
.cancelLoad(url, true)
.catch((e) => console.warn('Viewer Unload error', url, e))
.finally(() => {
if (this.loadedObjectsCache.has(url)) this.loadedObjectsCache.delete(url)
if (onObjectUnloaded) onObjectUnloaded(url)
})
}
}
public async loadObjectsWithAutoUnload(
objects: any[],
objectUrls: string[],
onLoad: (url: string, index: number) => void,
onError: (url: string, error: Error) => void,
signal: AbortSignal
) {
var objectsToUnload = _.difference(
[...this.loadedObjectsCache],
objects.map((o) => o.id as string)
)
var objectsToUnload = _.difference([...this.loadedObjectsCache], objectUrls)
await this.unloadObjects(objectsToUnload, signal)
await this.loadObjects(objects, onLoad, onError, signal)
await this.loadObjects(objectUrls, onLoad, onError, signal)
}
public async loadObjects(
objectsToLoad: any[],
objectUrls: string[],
onLoad: (url: string, index: number) => void,
onError: (url: string, error: Error) => void,
signal: AbortSignal
) {
console.log('Loading objects', objectsToLoad)
try {
//let index = 0
let index = 0
let promises = []
for (const speckleObject of objectsToLoad) {
for (const url of objectUrls) {
signal.throwIfAborted()
console.log('Attempting to load', speckleObject.id, speckleObject)
if (!this.loadedObjectsCache.has(speckleObject.id)) {
console.log('Attempting to load', url)
if (!this.loadedObjectsCache.has(url)) {
console.log('Object is not in cache')
// TODO: Here's were the viewer loads each object, this used to be done via URL but is now changed to use JSON objects instead (already deserialized)
// const promise = this.viewer
// .loadObjectAsync(speckleObject, this.config.authToken, false)
// .then(() => onLoad(speckleObject.id, index++))
// .catch((e: Error) => onError(speckleObject, e))
// promises.push(promise)
// If batchSize has been reached, wait till all promises resolve before continuing
const promise = this.viewer
.loadObjectAsync(url, this.config.authToken, false)
.then(() => onLoad(url, index++))
.catch((e: Error) => onError(url, e))
.finally(() => {
if (!this.loadedObjectsCache.has(url)) this.loadedObjectsCache.add(url)
})
promises.push(promise)
if (promises.length == this.config.batchSize) {
//this.promises.push(Promise.resolve(this.later(1000)))
await Promise.all(promises)
promises = []
}
if (!this.loadedObjectsCache.has(speckleObject.id))
this.loadedObjectsCache.add(speckleObject.id)
} else {
console.log('Object was already in cache/already loaded')
console.log('Object was already in cache')
}
}
await Promise.all(promises)
@@ -186,7 +174,6 @@ export default class ViewerHandler {
objects: res.objects
}
}
public zoom(objectIds?: string[]) {
this.viewer.zoom(objectIds)
}
@@ -194,7 +181,6 @@ export default class ViewerHandler {
public zoomExtents() {
this.viewer.zoom()
}
public async unIsolateObjects() {
if (this.state.isolatedObjects)
this.state = await this.viewer.unIsolateObjects(this.state.isolatedObjects, 'powerbi', true)
@@ -227,13 +213,13 @@ export default class ViewerHandler {
public getScreenPosition(worldPosition): { x: number; y: number } {
return projectToScreen(
this.viewer.getExtension(CameraController).controls.camera,
this.viewer.getExtension(CameraController).renderingCamera,
worldPosition
)
}
public dispose() {
this.viewer.getExtension(CameraController).controls.removeAllEventListeners()
this.viewer.getExtension(CameraController).dispose()
this.viewer.dispose()
this.viewer = null
}
+83 -61
View File
@@ -1,6 +1,10 @@
import powerbi from 'powerbi-visuals-api'
import { IViewerTooltip, IViewerTooltipData, SpeckleDataInput } from '../types'
import { formattingSettings as fs } from 'powerbi-visuals-utils-formattingmodel'
import {
createDataViewWildcardSelector,
DataViewWildcardMatchingOption
} from 'powerbi-visuals-utils-dataviewutils/lib/dataViewWildcard'
import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions
import { SpeckleVisualSettingsModel } from 'src/settings/visualSettingsModel'
@@ -11,18 +15,23 @@ export function validateMatrixView(options: VisualUpdateOptions): {
const matrixVew = options.dataViews[0].matrix
if (!matrixVew) throw new Error('Data does not contain a matrix data view')
let hasObject = false,
let hasStream = false,
hasParentObject = false,
hasObject = false,
hasColorFilter = false
matrixVew.rows.levels.forEach((level) => {
level.sources.forEach((source) => {
if (!hasStream) hasStream = source.roles['stream'] != undefined
if (!hasParentObject) hasParentObject = source.roles['parentObject'] != undefined
if (!hasObject) hasObject = source.roles['object'] != undefined
if (!hasColorFilter) hasColorFilter = source.roles['objectColorBy'] != undefined
})
})
if (!hasStream) throw new Error('Missing Stream ID input')
if (!hasParentObject) throw new Error('Missing Commit Object ID input')
if (!hasObject) throw new Error('Missing Object Id input')
return {
hasColorFilter,
view: matrixVew
@@ -56,7 +65,7 @@ function processObjectValues(
displayName: colInfo.displayName,
value: value.value.toString()
}
if (value.valueSourceIndex) objectData.push(propData)
objectData.push(propData)
})
return { data: objectData, shouldColor, shouldSelect }
}
@@ -117,50 +126,94 @@ export function processMatrixView(
settings: SpeckleVisualSettingsModel,
onSelectionPair: (objId: string, selectionId: powerbi.extensibility.ISelectionId) => void
): SpeckleDataInput {
const objectJsonToLoad = [],
const objectUrlsToLoad = [],
objectIds = [],
selectedIds = [],
colorByIds = [],
objectTooltipData = new Map<string, IViewerTooltip>()
// Assume this has color filter
matrixView.rows.root?.children?.forEach((colorByGroup) => {
const colorByValue = colorByGroup.value
console.log('Color by group', colorByValue, colorByGroup)
matrixView.rows.root.children.forEach((streamUrlChild) => {
const url = streamUrlChild.value
const colorGroup = createColorGroup(host, colorByGroup, matrixView)
streamUrlChild.children?.forEach((parentObjectIdChild) => {
const parentId = parentObjectIdChild.value
objectUrlsToLoad.push(`${url}/objects/${parentId}`)
colorByGroup.children.forEach((objectIdGroup) => {
const uniqueId = objectIdGroup.value
const jsonValue = objectIdGroup.values[0] // TODO: Json value is set as first value in capabilities.json
if (!hasColorFilter) {
processObjectIdLevel(parentObjectIdChild, host, matrixView).forEach((objRes) => {
objectIds.push(objRes.id)
onSelectionPair(objRes.id, objRes.selectionId)
if (objRes.shouldSelect) selectedIds.push(objRes.id)
if (objRes.color) {
let group = colorByIds.find((g) => g.color === objRes.color)
if (!group) {
group = {
color: objRes.color,
objectIds: []
}
colorByIds.push(group)
}
group.objectIds.push(objRes.id)
}
objectTooltipData.set(objRes.id, {
selectionId: objRes.selectionId,
data: objRes.data
})
})
} else {
if (previousPalette) host.colorPalette['colorPalette'] = previousPalette
parentObjectIdChild.children?.forEach((colorByChild) => {
const colorSelectionId = host
.createSelectionIdBuilder()
.withMatrixNode(colorByChild, matrixView.rows.levels)
.createSelectionId()
objectJsonToLoad.push(JSON.parse(jsonValue.value.toString()))
colorGroup.objectIds.push(uniqueId)
const color = host.colorPalette.getColor(colorByChild.value as string)
if (colorByChild.objects) {
console.log(
'⚠️COLOR NODE HAS objects',
colorByChild.objects,
colorByChild.objects.color?.fill
)
}
if (jsonValue.highlight) console.log(uniqueId, jsonValue)
var processedObject = processObjectNode(objectIdGroup, host, matrixView)
console.log(processedObject)
const colorSlice = new fs.ColorPicker({
name: 'selectorFill',
displayName: colorByChild.value.toString(),
value: {
value: color.value
},
selector: colorSelectionId.getSelector()
})
onSelectionPair(uniqueId.toString(), processedObject.selectionId)
const colorGroup = {
color: color.value,
slice: colorSlice,
objectIds: []
}
if (processedObject.shouldSelect) selectedIds.push(processedObject.id)
if (processedObject.shouldColor) colorGroup.objectIds.push(processedObject.id)
objectTooltipData.set(processedObject.id, {
selectionId: processedObject.selectionId,
data: processedObject.data
})
processObjectIdLevel(colorByChild, host, matrixView).forEach((objRes) => {
objectIds.push(objRes.id)
onSelectionPair(objRes.id, objRes.selectionId)
if (objRes.shouldSelect) selectedIds.push(objRes.id)
if (objRes.shouldColor) {
colorGroup.objectIds.push(objRes.id)
}
objectTooltipData.set(objRes.id, {
selectionId: objRes.selectionId,
data: objRes.data
})
})
if (colorGroup.objectIds.length > 0) colorByIds.push(colorGroup)
})
}
})
if (colorGroup.objectIds.length > 0) colorByIds.push(colorGroup)
})
// TODO: Code behavior without color filter
previousPalette = host.colorPalette['colorPalette']
return {
objectsToLoad: objectJsonToLoad,
objectsToLoad: objectUrlsToLoad,
objectIds,
selectedIds,
colorByIds: colorByIds.length > 0 ? colorByIds : null,
@@ -168,34 +221,3 @@ export function processMatrixView(
view: matrixView
}
}
function createColorGroup(
host: powerbi.extensibility.visual.IVisualHost,
colorByGroup: powerbi.DataViewMatrixNode,
matrixView: powerbi.DataViewMatrix
) {
const colorSelectionId = host
.createSelectionIdBuilder()
.withMatrixNode(colorByGroup, matrixView.rows.levels)
.createSelectionId()
const color = host.colorPalette.getColor(colorByGroup.value as string)
if (colorByGroup.objects) {
console.log('⚠️COLOR NODE HAS objects', colorByGroup.objects, colorByGroup.objects.color?.fill)
}
const colorSlice = new fs.ColorPicker({
name: 'selectorFill',
displayName: colorByGroup.value.toString(),
value: {
value: color.value
},
selector: colorSelectionId.getSelector()
})
const colorGroup = {
color: color.value,
slice: colorSlice,
objectIds: []
}
return colorGroup
}
+2 -1
View File
@@ -1,6 +1,7 @@
import { FilteringState } from '@speckle/viewer'
import { OrthographicCamera, PerspectiveCamera } from 'three'
export function projectToScreen(cam, loc) {
export function projectToScreen(cam: OrthographicCamera | PerspectiveCamera, loc) {
cam.updateProjectionMatrix()
const copy = loc.clone()
copy.project(cam)
+5
View File
@@ -20,6 +20,11 @@ import VisualConstructorOptions = powerbi.extensibility.visual.VisualConstructor
import VisualUpdateOptions = powerbi.extensibility.visual.VisualUpdateOptions
import IVisual = powerbi.extensibility.visual.IVisual
import ITooltipService = powerbi.extensibility.ITooltipService
import {
createDataViewWildcardSelector,
DataViewWildcardMatchingOption
} from 'powerbi-visuals-utils-dataviewutils/lib/dataViewWildcard'
import { ColorSelectorSettings } from 'src/settings/colorSettings'
// noinspection JSUnusedGlobalSymbols
export class Visual implements IVisual {