Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6e92c857a7 | |||
| 8edc01b7d6 | |||
| 6d5f638895 | |||
| ccecf7cb2b | |||
| 156c3a5c3a | |||
| 887dbb2344 | |||
| 556196a45f | |||
| 1bf6e76252 | |||
| b26801ef8a | |||
| 74ab91be3b |
@@ -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&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"></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.
|
||||
|
||||
Speckle’s 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.
|
||||
|
||||

|
||||
|
||||
# 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 ](https://speckle.xyz) ⇒ creating an account at our public server
|
||||
- [](https://marketplace.digitalocean.com/apps/speckle-server?refcode=947a2b5d7dc1) ⇒ deploying an instance in 1 click
|
||||
- [](https://app.speckle.systems) ⇒ creating an account at our public server
|
||||
|
||||
### Resources
|
||||
|
||||
@@ -41,56 +100,3 @@ Give Speckle a try in no time by:
|
||||
- [](https://speckle.guide/dev/) reference on almost any end-user and developer functionality
|
||||
|
||||

|
||||
|
||||
# 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:
|
||||
|
||||

|
||||
|
||||
### Checking the connector is loaded
|
||||
|
||||
Now open PowerBI and you should see `Speckle (beta)` appear in the data source.
|
||||
|
||||

|
||||
|
||||
## 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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
Generated
+111
-30
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user