Compare commits

..

3 Commits

Author SHA1 Message Date
izzy lyseggen 427e240648 ci: deploy filter tags 2022-06-10 12:12:20 +01:00
izzy lyseggen 99c67a0db9 chore(deps): stackoverflow and i are besties
testing out this workaround - will do a beta release
2022-06-10 12:00:00 +01:00
izzy lyseggen ba556d3268 ci: update tag parsing and ui build 2022-06-10 11:37:59 +01:00
260 changed files with 1268 additions and 21879 deletions
+25 -180
View File
@@ -21,7 +21,7 @@ jobs:
- persist_to_workspace:
root: ./
paths:
- speckle_connector/vue_ui
- speckle_connector/html
build-connector: # Reusable job for basic connectors
executor:
@@ -36,154 +36,31 @@ jobs:
- attach_workspace:
at: ./
- run:
name: Create Innosetup signing cert
name: Patch
shell: powershell.exe
command: |
echo $env:PFX_B64 > "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.txt"
certutil -decode "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.txt" "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.pfx"
- run:
name: Set Environment Variable
shell: powershell.exe
command: |
$tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "2.0.999" } else { $env:CIRCLE_TAG }
$semver = if($tag.Contains('/')) {$tag.Split("/")[0] } else { $tag }
command:
| # If no tag, use 0.0.0.1 and don't make any YML (for testing only!)
$tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "0.0.0" } else { $env:CIRCLE_TAG }
$semver = if($tag.Contains('/')) {$tag.Split("/")[1] } else { $tag }
$ver = if($semver.Contains('-')) {$semver.Split("-")[0] } else { $semver }
$version = "$($ver).$($env:WORKFLOW_NUM)"
python patch_version.py $semver
environment:
WORKFLOW_NUM: << pipeline.number >>
- run:
name: Build Installer
command: speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss /Sbyparam=$p
shell: cmd.exe #does not work in powershell
#- run:
# name: Patch
# shell: powershell.exe
# command:
# | # If no tag, use 0.0.0.1 and don't make any YML (for testing only!)
# $tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "0.0.0" } else { $env:CIRCLE_TAG }
# $semver = if($tag.Contains('/')) {$tag.Split("/")[1] } else { $tag }
# $ver = if($semver.Contains('-')) {$semver.Split("-")[0] } else { $semver }
# $channel = if($semver.Contains('-')) {$semver.Split("-")[1] } else { "latest" }
# $version = "$($ver).$($env:CIRCLE_BUILD_NUM)"
# New-Item -Force "speckle-sharp-ci-tools/Installers/sketchup/$channel.yml" -ItemType File -Value "version: $semver"
# echo $version
# python patch_version.py $semver
# speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss
$channel = if($semver.Contains('-')) {$semver.Split("-")[1] } else { "latest" }
$version = "$($ver).$($env:CIRCLE_BUILD_NUM)"
New-Item -Force "speckle-sharp-ci-tools/Installers/sketchup/$channel.yml" -ItemType File -Value "version: $version"
echo $version
python patch_version.py $version
speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools/Installers
build-connector-mac:
macos:
xcode: 12.5.1
parameters:
projname:
type: string
default: ""
slug:
type: string
default: ""
installer:
type: boolean
default: false
converter-files:
type: string
default: ""
installername:
type: string
default: ""
build-config:
type: string
default: Release
bundlename:
type: string
default: ""
steps:
- checkout
- attach_workspace:
at: ./
- run:
name: Install dotnet
command: |
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel Current
$HOME/.dotnet/dotnet --version
$HOME/.dotnet/dotnet --list-runtimes
$HOME/.dotnet/dotnet --list-sdks
- run:
name: Create installer target dir
command: |
mkdir -p speckle-sharp-ci-tools/Installers/<< parameters.slug >>
- run:
name: Set Environment Variable
command: |
TAG=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "2.0.999"; fi;)
SEMVER=$(echo "$TAG" | sed -e 's/\/[a-zA-Z-]*//')
VER=$(echo "$SEMVER" | sed -e 's/-.*//')
VERSION=$(echo $VER.$WORKFLOW_NUM)
python3 patch_version.py $SEMVER
environment:
WORKFLOW_NUM: << pipeline.number >>
- run:
name: Zip Connector files
command: |
zip -r << parameters.slug >>-mac.zip "./speckle_connector" "./speckle_connector.rb"
# Copy installer files
- run:
name: Copy files to installer
command: |
mkdir -p speckle-sharp-ci-tools/Mac/<< parameters.installername >>/.installationFiles/
cp << parameters.slug >>-mac.zip speckle-sharp-ci-tools/Mac/<<parameters.installername>>/.installationFiles
# Create installer
- run:
name: Exit if External PR
command: if [ "$CIRCLE_PR_REPONAME" ]; then circleci-agent step halt; fi
- run:
name: Build Mac installer
command: ~/.dotnet/dotnet publish speckle-sharp-ci-tools/Mac/<<parameters.installername>>/<<parameters.installername>>.sln -r osx-x64 -c Release
- run:
name: Zip installer
command: |
cd speckle-sharp-ci-tools/Mac/<<parameters.installername>>/bin/Release/net6.0/osx-x64/publish/
zip -r <<parameters.slug>>.zip ./
- store_artifacts:
path: speckle-sharp-ci-tools/Mac/<<parameters.installername>>/bin/Release/net6.0/osx-x64/publish/<<parameters.slug>>.zip
- run:
name: Copy to installer location
command: |
TAG=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "2.0.999"; fi;)
SEMVER=$(echo "$TAG" | sed -e 's/\/[a-zA-Z-]*//')
VER=$(echo "$SEMVER" | sed -e 's/-.*//')
VERSION=$(echo $VER.$WORKFLOW_NUM)
cp speckle-sharp-ci-tools/Mac/<<parameters.installername>>/bin/Release/net6.0/osx-x64/publish/<<parameters.slug>>.zip speckle-sharp-ci-tools/Installers/<< parameters.slug >>/<<parameters.slug>>-$SEMVER.zip
environment:
WORKFLOW_NUM: << pipeline.number >>
- when:
condition: << pipeline.git.tag >>
steps:
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools/Installers
get-ci-tools: # Clones our ci tools and persists them to the workspace
docker:
- image: cimg/base:2021.01
steps:
- add_ssh_keys:
fingerprints:
- "03:2e:ee:4f:14:67:2b:88:32:e8:cc:f0:cb:df:92:29"
- run:
name: I know Github as a host
command: |
mkdir ~/.ssh
ssh-keyscan github.com >> ~/.ssh/known_hosts
- run:
name: Clone
command: git clone git@github.com:specklesystems/speckle-sharp-ci-tools.git speckle-sharp-ci-tools
command: git clone https://$GITHUB_TOKEN@github.com/specklesystems/speckle-sharp-ci-tools.git speckle-sharp-ci-tools
- persist_to_workspace:
root: ./
paths:
@@ -193,29 +70,22 @@ jobs:
paths:
- speckle-sharp-ci-tools
deploy-manager2:
deploy: # Uploads all installers found to S3
docker:
- image: mcr.microsoft.com/dotnet/sdk:6.0
parameters:
slug:
type: string
os:
type: string
extension:
type: string
- image: cimg/base:2021.01
steps:
- checkout
- attach_workspace:
at: ./
- run:
name: Install Manager Feed CLI
command: dotnet tool install --global Speckle.Manager.Feed
- run:
name: Upload new version
command: |
TAG=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "0.0.0"; fi;)
SEMVER=$(echo "$TAG" | sed -e 's/\/[a-zA-Z-]*//')
/root/.dotnet/tools/Speckle.Manager.Feed deploy -s << parameters.slug >> -v ${SEMVER} -u https://releases.speckle.dev/installers/<< parameters.slug >>/<< parameters.slug >>-${SEMVER}.<< parameters.extension >> -o << parameters.os >> -f speckle-sharp-ci-tools/Installers/<< parameters.slug >>/<< parameters.slug >>-${SEMVER}.<< parameters.extension >>
name: List contents
command: ls -R speckle-sharp-ci-tools/Installers
- aws-s3/copy:
arguments: "--recursive --endpoint=https://$SPACES_REGION.digitaloceanspaces.com --acl public-read"
aws-access-key-id: SPACES_KEY
aws-region: SPACES_REGION
aws-secret-access-key: SPACES_SECRET
from: '"speckle-sharp-ci-tools/Installers/"'
to: s3://speckle-releases/installers/
workflows:
build-and-deploy:
@@ -238,39 +108,14 @@ workflows:
filters:
tags:
only: /.*/
context: innosetup
- build-connector-mac:
slug: sketchup
- deploy:
requires:
- get-ci-tools
- build-ui
filters:
tags:
only: /.*/
installername: SpeckleSketchUpInstall
- deploy-manager2:
context: do-spaces-speckle-releases
slug: sketchup
os: Win
extension: exe
requires:
- build-connector
filters:
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
- deploy-manager2:
context: do-spaces-speckle-releases
slug: sketchup
os: OSX
extension: zip
requires:
- build-connector-mac
filters:
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
-38
View File
@@ -1,38 +0,0 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
name: Ruby
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['2.7']
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
# change this to (see https://github.com/ruby/setup-ruby#versioning):
# uses: ruby/setup-ruby@v1
uses: ruby/setup-ruby@0a29871fe2b0200a17a4497bae54fe5df0d973aa # v1.115.3
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Run tests
run: bundle exec rake
+1 -19
View File
@@ -10,23 +10,8 @@
settings.json
# vue app build dist folder
speckle_connector/vue_ui
speckle_connector/html
# speckle-sharp-ci-tools
/speckle-sharp-ci-tools
# _sqlite3
/_sqlite3/.vs
/_sqlite3/Release (2.7)
/_sqlite3/Release (2.5)
/_sqlite3/Release (2.2)
/_sqlite3/Release (2.0)
/_sqlite3/Debug (2.7)
/_sqlite3/Debug (2.5)
/_sqlite3/Debug (2.2)
/_sqlite3/Debug (2.0)
*.gem
*.rbc
/.config
@@ -39,9 +24,6 @@ speckle_connector/html
/test/version_tmp/
/tmp/
# IDE
.idea
# Used by dotenv library to load environment variables.
.env
-3
View File
@@ -1,3 +0,0 @@
[submodule "_sqlite3"]
path = _sqlite3
url = git@github.com:specklesystems/sketchup-sqlite3.git
+25 -108
View File
@@ -1,132 +1,49 @@
require:
- rubocop-sketchup
- rubocop-minitest
- rubocop-rake
AllCops:
TargetRubyVersion: 2.5
DisabledByDefault: false
NewCops: enable
DisplayCopNames: true
ExtraDetails: true
TargetRubyVersion: 2.7
EnabledByDefault: true
AutoCorrect: true
SuggestExtensions: false
Exclude:
- '_tools/jf_RubyPanel.rb'
- '_tools/jf_RubyPanel/**/*.rb'
- '_tools/su_attributes.rb'
- '_tools/su_attributes/**/*.rb'
- '_tools/su_attributes/**/*.rb'
- '_sqlite3/**/*.rb'
- 'ui/**/*'
- 'speckle_connector/src/ext/**/*.rb'
- 'vendor/bundle/**/*'
- 'tests/**/*.rb'
SketchUp:
SourcePath: .
TargetSketchUpVersion: 2021
Exclude: # Exclude common folders.
- 'tests/**/*'
- 'benchmarks/**/*'
- '_tools/**/*'
- 'Rakefile'
Layout:
Style/StringLiterals:
Enabled: true
EnforcedStyle: double_quotes
Layout/IndentationStyle:
EnforcedStyle: spaces
IndentationWidth: 2
# If DisabledByDefault is set to true then we need to enable the SketchUp
# related departments:
SketchupDeprecations:
Style/StringLiteralsInInterpolation:
Enabled: true
EnforcedStyle: double_quotes
SketchupPerformance:
Enabled: true
Layout/LineLength:
Max: 120
SketchupRequirements:
Enabled: true
SketchupSuggestions:
Enabled: true
SketchupBugs:
Enabled: true
SketchupRequirements/FileStructure:
Lint/ConstantResolution:
Enabled: false
SketchupSuggestions/ModelEntities:
Style/Copyright:
Enabled: false
Style/DocumentationMethod:
Enabled: false
Metrics/AbcSize:
Max: 30
Enabled: false
Metrics/BlockLength:
# Exclude spec tests
Exclude:
- "**/*_spec.rb"
Enabled: false
Metrics/ClassLength:
Enabled: false
Metrics/ModuleLength:
Enabled: false
Metrics/MethodLength:
Max: 20
Metrics/ClassLength:
Max: 200
Layout/EndOfLine:
Enabled: false
EnforcedStyle: lf
Minitest/MultipleAssertions:
Max: 5
Naming/MethodParameterName:
AllowedNames: [x, y, z, id]
Naming/VariableNumber:
EnforcedStyle: snake_case
# SketchUp 2017 uses Ruby 2.2 where safe navigation is not available
Style/SafeNavigation:
Enabled: false
Style/AndOr:
Metrics/ParameterLists:
Enabled: false
Style/Documentation:
Exclude:
- "*tests/**/*_spec.rb"
- "*tests/**/*_test.rb"
Style/Not:
Metrics/CyclomaticComplexity:
Enabled: false
Style/NumericLiterals:
Enabled: false
Style/NumericPredicate:
EnforcedStyle: comparison
Style/Proc:
Enabled: false
Style/RedundantReturn:
Enabled: false
# SketchUp 2017 uses Ruby 2.2 where safe navigation is not available
Style/SlicingWithRange:
Enabled: false
# SketchUp 2017 uses Ruby 2.2 where transform_values is not available
Style/HashTransformValues:
Enabled: false
# SketchUp 2017 uses Ruby 2.2 where transform_keys is not available
Style/HashTransformKeys:
Enabled: false
# SketchUp 2017 uses Ruby 2.2 where block needs to be wrapped in begin/end if ensure can be used
Style/RedundantBegin:
Metrics/PerceivedComplexity:
Enabled: false
+11 -24
View File
@@ -1,29 +1,16 @@
# frozen_string_literal: true
source 'https://rubygems.org'
source "https://rubygems.org"
# gem "rake", "~> 13.0"
gem "rubocop", "~> 1.7"
group :development do
# mini tests for ruby classes
gem 'minitest'
# Git hooks manager
gem 'overcommit', require: false
# Pry is a runtime developer console and IRB alternative with powerful introspection capabilities.
# Pry aims to be more than an IRB replacement. It is an attempt to bring REPL driven programming to the Ruby language.
gem 'pry'
# Make-like program implemented in Ruby. Tasks and dependencies are specified in standard Ruby syntax.
gem 'rake'
# RuboCop is a Ruby static code analyzer (a.k.a. linter) and code formatter.
gem 'rubocop'
# A RuboCop extension focused on enforcing Minitest best practices and coding conventions.
gem 'rubocop-minitest'
# A RuboCop plugin for Rake.
gem 'rubocop-rake'
# Code analysis for SketchUp extensions using the SketchUp Ruby API.
gem 'rubocop-sketchup'
# wraps around static analysis gems such as Reek, Flay and Flog to provide a quality report of your Ruby code.
gem 'rubycritic', '~> 4.3', '>= 4.3.3', require: false
# Auto completions for SketchUp API.
gem 'sketchup-api-stubs'
# Aid with common SketchUp extension tasks.
gem 'skippy', '~> 0.4.1.a'
gem "minitest"
gem "sketchup-api-stubs"
gem "solargraph"
end
gem "sqlite3", "~> 1.4"
+52 -111
View File
@@ -1,133 +1,74 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
axiom-types (0.1.1)
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
childprocess (4.1.0)
coderay (1.1.3)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
docile (1.4.0)
equalizer (0.0.11)
erubi (1.11.0)
flay (2.13.0)
erubi (~> 1.10)
path_expander (~> 1.0)
ruby_parser (~> 3.0)
sexp_processor (~> 4.0)
flog (4.6.6)
path_expander (~> 1.0)
ruby_parser (~> 3.1, > 3.1.0)
sexp_processor (~> 4.8)
git (1.18.0)
addressable (~> 2.8)
rchardet (~> 1.8)
ice_nine (0.11.2)
iniparse (1.5.0)
kwalify (0.7.2)
launchy (2.5.0)
addressable (~> 2.7)
method_source (1.0.0)
minitest (5.16.3)
naturally (2.2.1)
overcommit (0.59.1)
childprocess (>= 0.6.3, < 5)
iniparse (~> 1.4)
rexml (~> 3.2)
parallel (1.22.1)
parser (3.1.2.1)
backport (1.2.0)
benchmark (0.1.1)
diff-lcs (1.4.4)
e2mmap (0.1.0)
jaro_winkler (1.5.4)
kramdown (2.3.1)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
minitest (5.14.4)
nokogiri (1.12.5-x64-mingw32)
racc (~> 1.4)
nokogiri (1.12.5-x86_64-linux)
racc (~> 1.4)
parallel (1.20.1)
parser (3.0.2.0)
ast (~> 2.4.1)
path_expander (1.1.1)
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (5.0.1)
rainbow (3.1.1)
rake (13.0.6)
rchardet (1.8.0)
reek (6.1.1)
kwalify (~> 0.7.0)
parser (~> 3.1.0)
rainbow (>= 2.0, < 4.0)
regexp_parser (2.6.0)
racc (1.6.0)
rainbow (3.0.0)
regexp_parser (2.1.1)
reverse_markdown (2.0.0)
nokogiri
rexml (3.2.5)
rubocop (1.7.0)
rubocop (1.19.1)
parallel (~> 1.10)
parser (>= 2.7.1.5)
parser (>= 3.0.0.0)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml
rubocop-ast (>= 1.2.0, < 2.0)
rubocop-ast (>= 1.9.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (1.4.1)
parser (>= 2.7.1.5)
rubocop-minitest (0.23.0)
rubocop (>= 0.90, < 2.0)
rubocop-rake (0.6.0)
rubocop (~> 1.0)
rubocop-sketchup (1.3.0)
rubocop (>= 0.82, < 2.0)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.11.0)
parser (>= 3.0.1.1)
ruby-progressbar (1.11.0)
ruby_parser (3.19.1)
sexp_processor (~> 4.16)
rubycritic (4.7.0)
flay (~> 2.8)
flog (~> 4.4)
launchy (>= 2.0.0)
parser (>= 2.6.0)
rainbow (~> 3.0)
reek (~> 6.0, < 7.0)
ruby_parser (~> 3.8)
simplecov (>= 0.17.0)
tty-which (~> 0.4.0)
virtus (~> 1.0)
sexp_processor (4.16.1)
simplecov (0.21.2)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
sketchup-api-stubs (0.7.8)
skippy (0.4.3.a)
git (~> 1.3)
naturally (~> 2.1)
thor (~> 0.19)
thor (0.20.3)
thread_safe (0.3.6)
tty-which (0.4.2)
unicode-display_width (1.8.0)
virtus (1.0.5)
axiom-types (~> 0.1)
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9)
sketchup-api-stubs (0.7.7)
solargraph (0.43.0)
backport (~> 1.2)
benchmark
bundler (>= 1.17.2)
diff-lcs (~> 1.4)
e2mmap
jaro_winkler (~> 1.5)
kramdown (~> 2.3)
kramdown-parser-gfm (~> 1.1)
parser (~> 3.0)
reverse_markdown (>= 1.0.5, < 3)
rubocop (>= 0.52)
thor (~> 1.0)
tilt (~> 2.0)
yard (~> 0.9, >= 0.9.24)
sqlite3 (1.4.2)
thor (1.1.0)
tilt (2.0.10)
unicode-display_width (2.0.0)
yard (0.9.26)
PLATFORMS
x64-mingw32
x64-unknown
x86_64-linux
DEPENDENCIES
minitest
overcommit
pry
rake
rubocop
rubocop-minitest
rubocop-rake
rubocop-sketchup
rubycritic (~> 4.3, >= 4.3.3)
rubocop (~> 1.7)
sketchup-api-stubs
skippy (~> 0.4.1.a)
solargraph
sqlite3 (~> 1.4)
BUNDLED WITH
2.3.25
2.2.26
+30 -86
View File
@@ -41,42 +41,27 @@ 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/user/blender.html) reference on almost any end-user and developer functionality
# Repo structure
# Repo structure
This is the beginning of the Speckle SketchUp Connector. It is still in very early development and is not ready for general use.
This repo is split into three parts:
### 1. **Speckle Connector extension**
Includes the `ruby` source files to run extension on SketchUp environment. SketchUp Extensions are composed of
a **.rb** file as entry and **folder** that .rb file refers to. In our case entry file is `speckle_connector.rb`
that responsible to register Speckle Connector extension to SketchUp and also it shows address to where extension
will start to read extension. Source folder is `speckle_connector`.
This repo is split into two parts: `speckle_connector` which is the Ruby SketchUp plugin and `ui` which is the Vue frontend.
### 2. **User Interface**
## Usage
Includes the `Vue` frontend lives in the `ui` folder.
> NOTE: this connector is still in early development and isn't ready for general use.
### 3. **SketchUp Sqlite3 extension** [submodule](https://github.com/specklesystems/sketchup-sqlite3)
Copy the whole `speckle_connector` folder to you SketchUp Plugins folder. You will likely find this at:
Includes source codes of base `SQLite3` C/C++ library and `ruby` compiler files to be able to run SQLite3
functionality on SketchUp in the same ruby module like `SpeckleConnector::Sqlite3::Database`. By this way
we use extensions as native part of the source `ruby` code.
C:\Users\{YOU}\AppData\Roaming\SketchUp\SketchUp 2021\SketchUp\Plugins
After building `sqlite3.sln` file, compiled `sqlite3.so` (for Windows) and `sqlite3.bundle` (for OSX) dynamic library files are created
by solution to place them into source code into `speckle_connector/src/ext`. Building this project should be only
happen when SketchUp starts to support newer Ruby versions (currently it is `2.7`).
## Contribution Guide
You'll need to serve the ui before launching the connector:
Before start to contribute, it is better to understand how align with other contributors. It will make easier job
of reviewer when you submit an issue or PR. If it is your first repo to contribute Speckle environment make sure that you read
[Contribution Guideline](https://github.com/specklesystems/speckle-sharp/blob/main/.github/CONTRIBUTING.md).
cd ui
npm install
npm run serve
Additionally as mentioned on [Repo Structure](#3-sketchup-sqlite3-extension-submodulehttpsgithubcomspecklesystemssketchup-sqlite3),
this repo includes a submodule. Contributions on this source files should be done on the [sketchup-sqlite](https://github.com/specklesystems/sketchup-sqlite3)
by creating issues and PRs on it. If it is your first time works with submodules, please read [git docs](https://git-scm.com/book/en/v2/Git-Tools-Submodules)
briefly to get some insight about it.
## Development
@@ -104,63 +89,43 @@ Clone this repo and run:
This will install all the necessary packages for the connector.
Next, install the Sketchup Ruby Debugger. You can find installation instructions
[here](https://github.com/SketchUp/sketchup-ruby-debugger/blob/main/README.md).
This will involve downloading the `dll` and copying it into the SketchUp installation
directory:
Next, install the Sketchup Ruby Debugger. You can find installation instructions [here](https://github.com/SketchUp/sketchup-ruby-debugger/blob/main/README.md). This will involve downloading the `dll` and copying it into the SketchUp installation directory:
C:\Program Files\SketchUp\SketchUp 20XX\
C:\Program Files\SketchUp\SketchUp 2021\
You can now open up the repo in VS Code or you can use JetBrains' tools RubyMine and Webstorm.
You can now open up the repo in VS Code.
If you will use VS Code, make sure you've installed the Ruby extension for VS Code.
Make sure you've installed the Ruby extension for VS Code.
### Loading the Speckle Connector Plugin
### Loading the Plugin
1. Find already prepared `speckle_connector_loader.rb` file on the `_tools`
folder.
2. Copy this Ruby file into your SketchUp Plugins directory. You will likely find this at:
`C:\Users\{YOU}\AppData\Roaming\SketchUp\SketchUp 20XX\SketchUp\Plugins`
3. Update below line on the copied file with your local git file.
```ruby
speckle_path = File.join(home_folder, 'Git', 'Speckle', 'speckle-sketchup')
```
By this way SketchUp will directly read your local repository. Do not forget,
this file also loads additional tools on the `_tools` folder.
Those are will be only available on dev mode.
To tell SketchUp to load the plugin from wherever you happen to be developing, you'll need to create a ruby file with the following contents:
Due to the fact that Ruby is interpreted language, so you can reload your file(s) when
you changed them. There are different kinds of ways to reload them.
```ruby
$LOAD_PATH << 'C:\YOUR\PATH\TO\THE\sketchup_connector'
require 'speckle_connector.rb'
```
1. To reload the whole plugin files while SketchUp is running, open up the Ruby console
and run the following:
```ruby
SpeckleConnector.reload
```
2. To reload only specific files, use `jf ruby toolbar` plugin that already available
on SketchUp toolbar.
Drop this Ruby file into your SketchUp Plugins directory. You will likely find this at:
### User Interface
C:\Users\{YOU}\AppData\Roaming\SketchUp\SketchUp 2021\SketchUp\Plugins
If it is your first time you cloned the project and willing to see Speckle UI, you
should make sure that you compiled the `vue.js` project in the `ui` folder.
To reload the plugin while SketchUp is running, open up the Ruby console and run the following:
To run the `ui`, create a `.env` based on `.env-example` and paste in your
Speckle token. Then:
SpeckleSystems::SpeckleConnector.reload
To run the `ui`, create a `.env` based on `.env-example` and paste in your Speckle token. Then:
cd ui
npm run serve
### Debugging
To run SketchUp in debug mode, you will run the task specified in `tasks.json`.
Before you do this, make sure your integrated shell for tasks is using powershell.
You can specify this by adding the following option to your workspace's `settings.json`
To run SketchUp in debug mode, you will run the task specified in `tasks.json`. Before you do this, make sure your integrated shell for tasks is using powershell. You can specify this by adding the following option to your workspace's `settings.json`
"terminal.integrated.automationShell.windows": "powershell.exe",
To start the task, use the keyboard shortcut `ctrl` + `shift` + `p` to open up
the Command Palette. Search for `Tasks: Run Task` and select it:
To start the task, use the keyboard shortcut `ctrl` + `shift` + `p` to open up the Command Palette. Search for `Tasks: Run Task` and select it:
![command palette](https://user-images.githubusercontent.com/7717434/135051668-35fee34e-5270-4b83-9c7b-dabb872370ee.png)
@@ -168,30 +133,9 @@ Then choose the `Debug Sketchup 2021` task to run it:
![debug sketchup task](https://user-images.githubusercontent.com/7717434/135051777-4c350a62-45fb-400e-9b24-4fbb02331b83.png)
Once Sketchup has launched, start the `Listen for rdebug-ide` debug configuration.
Once the debugger has connected, you'll be able to debug the connector normally.
Once Sketchup has launched, start the `Listen for rdebug-ide` debug configuration. Once the debugger has connected, you'll be able to debug the connector normally.
Make sure you run the `ui` before starting the SketchUp Connector
cd ui
npm run serve
### Code Quality
Tracking your code quality before merging any code to `main` branch might not seem at the
first time crucial, but when repo became huge, you might have many spaghetti code and technical
depth. It is always better to keep your work tough from the beginning. For this reason some
workflows have already setup on CI, those workflows must be passed before considering to
merge.
To track your code quality locally,
1. Make sure that you do not have any RuboCop issue, run below
```ruby
bundle exec rake
```
2. To check overall state of repository by RubyCritic, run below
```ruby
rake rubycritic
```
npm run serve
-52
View File
@@ -1,52 +0,0 @@
# frozen_string_literal: true
require 'rake/testtask'
require 'rubocop/rake_task'
require 'rubycritic/rake_task'
module SpeckleConnector
# Custom utility functions for rake tasks
module RakeUtils
module_function
# Find ruby files that were changed from `main` to the latest revision
def changed_rb_files(previous_revision: 'main', latest_revision: '')
range = latest_revision.empty? ? previous_revision : "#{latest_revision}..#{previous_revision}"
command = "git diff #{range} --name-only"
changed_files = `#{command}`.split("\n")
# filter changed files with ruby files (.rb), Gemfile and Rakefile.
filtered_files = changed_files.grep(/.*\.rb$|Gemfile|Rakefile/)
filtered_files.select { |file| File.exist?(file) }
end
end
end
# Add default rubocop task
RuboCop::RakeTask.new(:default)
# Add task to only verify ruby files that are different than in the `main` branch
desc('Run rubocop on changed files')
RuboCop::RakeTask.new(:rubocop_changed) do |t|
t.patterns = FileList.new(SpeckleConnector::RakeUtils.changed_rb_files)
end
# Glob pattern to match source files. Defaults to FileList['.'].
ruby_critic_paths = FileList[
'speckle_connector/**/*.rb',
'speckle_connector.rb',
'tests/**/*.rb'] -
FileList[
'_tools/**/*.rb',
'speckle_connector/src/ext/**/*.rb',
]
# for local
RubyCritic::RakeTask.new('rubycritic') do |task|
task.paths = ruby_critic_paths
end
# for CI
RubyCritic::RakeTask.new('rubycritic-ci') do |task|
task.options = '--mode-ci --format console --no-browser --branch main'
task.paths = ruby_critic_paths
end
Submodule _sqlite3 deleted from 800dd5e366
-25
View File
@@ -1,25 +0,0 @@
# Tools
This folder stores the external tools and helper scripts to make easier life of the developer,
they are not the part of the main functionality of the Speckle.
Tools and scripts inside the folder will be loaded with `sketchup_connector_loader.rb` file.
In order to load your own `.rb` files please add this file names into list in the loader.
````ruby
...
files = %w[speckle_connector jf_RubyPanel su_attributes <put-your-file-here>]
# This line placed before loading started.
files.each do |ruby_file|
puts "Loading #{ruby_file}"
begin
require ruby_file
rescue LoadError
puts "Could not load #{ruby_file}"
end
end
````
Track load status of your tools and scripts on the ruby console when SketchUp UI initializing.
-29
View File
@@ -1,29 +0,0 @@
# frozen_string_literal: true
# #-------------------------------------------------------------------------------------------------
# *************************************************************************************************
# RubyPanel Toolbar (C) 2007 jim.foltz@gmail.com
#
# With special thanks to Chris Phillips (Sketchy Physics)
# for the Win32API code examples.
#
# 2011-01-05 <jim.foltz@gmail.com>
# * Changed Toolbar name from "Ruby COnsole" to "Ruby Toolbar" (TT)
# http://forums.sketchucation.com/viewtopic.php?f=323&t=1542&p=298587#p298587
# * Wrapped in addition module RubyToolbar
# * Use $suString.GetSting to get proper "Ruby Console" name string.
# * Better check if TB was previously visible
# * Use UI.start_timer to restore Toolbar
# ICONS: located in the subfolder "rubytoolbar"
# MODIFICATION: by Fredo6 for compliance with SU 2014 (and no dependency on Win32API) - 18 Sep 2013
# *************************************************************************************************
#-------------------------------------------------------------------------------------------------
require 'sketchup'
require 'extensions'
ext = SketchupExtension.new('Ruby Toolbar', 'jf_RubyPanel/rubytoolbar.rb')
ext.creator = 'Jim Foltz <jim.foltz@gmail.com>'
ext.description = 'Toolbar for manipulating the Ruby Console. Compatible with SketchUp 2014'
ext.version = '2014'
Sketchup.register_extension(ext, true)
Binary file not shown.

Before

Width:  |  Height:  |  Size: 934 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

-89
View File
@@ -1,89 +0,0 @@
# frozen_string_literal: true
#-------------------------------------------------------------------------------------------------------------------------------------------------
# RubyPanel Toolbar (C) 2007 jim.foltz@gmail.com
# Permission to use, copy, modify, and distribute this software for # any purpose and without fee is hereby granted,
# provided that the above copyright notice appear in all copies.
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
# Description: Manage the loading of Ruby files and display of the Ruby console
# CREDITS: Special thanks to Chris Phillips (Sketchy Physics) for the Win32API code examples
# Revision: 3 Aug 2009, by Fredo6
# ICONS: located in the subfolder "rubytoolbar"
# MODIFICATION: by Fredo6 for compliance with SU 2014 (and no dependency on Win32API) - 18 Sep 2013
#-------------------------------------------------------------------------------------------------------------------------------------------------
require 'English'
require 'sketchup'
module JF_RubyToolbar
# Load the toolbar icons and commands, and do some initialization
def self.load_toolbar
@last_dir = "#{$LOAD_PATH[0]}/"
@last_dir = @last_dir.gsub('/', '\\\\\\\\')
@last_dir = File.join($JF_RUBYTOOLBAR, 'speckle_connector')
curdir = File.dirname __FILE__
# create toolbar
tb = UI::Toolbar.new 'Ruby Toolbar'
# Toggle console
cmd = UI::Command.new('Show/Hide') { SKETCHUP_CONSOLE.visible? ? SKETCHUP_CONSOLE.hide : SKETCHUP_CONSOLE.show }
cmd.large_icon = cmd.small_icon = File.join(curdir, 'rubypanel.png')
cmd.status_bar_text = cmd.tooltip = 'Show/Hide Ruby Console'
tb.add_item cmd
# Clear Console
cmd = UI::Command.new('Clear') { SKETCHUP_CONSOLE.clear }
cmd.status_bar_text = cmd.tooltip = 'Clear Console'
cmd.large_icon = cmd.small_icon = File.join(curdir, 'Delete24.png')
tb.add_item cmd
# Load a Ruby script
cmd = UI::Command.new('LoadScript') { load_script }
cmd.large_icon = cmd.small_icon = File.join(curdir, 'doc_ruby.png')
cmd.tooltip = cmd.status_bar_text = 'Load Script'
tb.add_item cmd
# Reload the last Ruby Script
@cmd_reload = UI::Command.new('Reload') { load_script @last_file }
@cmd_reload.large_icon = @cmd_reload.small_icon = File.join(curdir, 'reload.png')
@cmd_reload.status_bar_text = @cmd_reload.tooltip = 'Reload Script'
tb.add_item @cmd_reload
# Open the SU plugins directory panel
cmd = UI::Command.new('PluginsDir') { UI.openURL @last_dir }
cmd.tooltip = cmd.status_bar_text = 'Browse Plugins Folder'
cmd.large_icon = cmd.small_icon = File.join(curdir, 'open_folder.png')
tb.add_item cmd
# showing the toolbar
tb.get_last_state == -1 ? tb.show : tb.restore
end
# Load a script file - if <file> is nil, open the dialog panel to select the file
def self.load_script(file = nil)
file ||= UI.openpanel 'Load Script', @last_dir, '*.rb*'
return unless file
begin
load file
Sketchup.set_status_text "#{File.basename(file)} loaded (#{Time.now.strftime('%H:%M:%S')})"
@last_file = file
@last_dir = "#{File.dirname(file)}/"
@last_dir = @last_dir.gsub('/', '\\\\\\\\')
@cmd_reload.status_bar_text = @cmd_reload.tooltip = "Reload Script: #{File.basename(file)}"
rescue StandardError
UI.messagebox("Couldn't load #{File.basename(file)}: #{$ERROR_INFO}")
end
end
# Loading the toolbar once
unless file_loaded?('RubyToolbar.rb')
load_toolbar
file_loaded('RubyToolbar.rb')
end
end
-44
View File
@@ -1,44 +0,0 @@
# frozen_string_literal: true
# The purpose of this file is customizing environment of the developer on SketchUp.
# Each developer can customize it's own loader(this file), by this way developer can load their helper tools
# and helper methods ONLY in dev mode.
# Change the base folder and copy this file to Sketchup Plugins directory
# If you need to test in several versions of SketchUp, create symlinks to this file
# ( AppData\Roaming\SketchUp\SketchUp <version>\SketchUp\Plugins )
# Create a link to Plugins folder with this command
# rubocop:disable Layout/LineLength
# New-Item -ItemType SymbolicLink -Path '~\AppData\Roaming\SketchUp\SketchUp 2022\SketchUp\Plugins\speckle_connector_loader.rb' -Target ~\Git\Speckle\speckle-sketchup\_tools\speckle_connector_loader.rb
# rubocop:enable Layout/LineLength
SKETCHUP_CONSOLE.show # if you want to show Ruby console on startup
# base location of your repos - will be merged with specific repos in next step
home_folder = File.expand_path('~')
# If you use some other location for your repository, you can change it here
# but make sure it is not committed as it will change thi setting for all
# users that use the default setup. Eg:
# Add Speckle folder - uncomment the one you need
speckle_path = File.join(home_folder, 'Git', 'Speckle', 'speckle-sketchup')
$LOAD_PATH << speckle_path
$LOAD_PATH << File.join(speckle_path, '_tools')
# Defining this path will help to tool to browse related source file directly when
# developer attempted to reload/load file.
# rubocop:disable Style/GlobalVars
$JF_RUBYTOOLBAR = speckle_path
# rubocop:enable Style/GlobalVars
files = %w[speckle_connector jf_RubyPanel su_attributes]
files.each do |ruby_file|
puts "Loading #{ruby_file}"
begin
require ruby_file
rescue LoadError
puts "Could not load #{ruby_file}"
end
end
-49
View File
@@ -1,49 +0,0 @@
# Copyright 2014-2021, Trimble Inc.
#
# License: The MIT License (MIT)
#
# A SketchUp Ruby Extension that surfaces attributes attached to components.
# More info at https://github.com/SketchUp/sketchup-attribute-helper
require 'sketchup.rb'
require 'extensions.rb'
#-------------------------------------------------------------------------------
module Trimble
module AttributeHelper
### CONSTANTS ### ------------------------------------------------------------
# Plugin information
PLUGIN_ID = 'AttributeHelper'.freeze
PLUGIN_NAME = 'SketchUp Attribute Helper'.freeze
PLUGIN_VERSION = '1.0.3'.freeze
# Resource paths
FILENAMESPACE = File.basename(__FILE__, '.*')
PATH_ROOT = File.dirname(__FILE__).freeze
PATH = File.join(PATH_ROOT, FILENAMESPACE).freeze
### EXTENSION ### ------------------------------------------------------------
unless file_loaded?(__FILE__)
loader = File.join( PATH, 'core.rb' )
ex = SketchupExtension.new(PLUGIN_NAME, loader)
ex.description = 'Visually inspect nested attributes in SketchUp.'
ex.version = PLUGIN_VERSION
ex.copyright = 'Trimble Inc © 2015-2021'
ex.creator = 'SketchUp'
Sketchup.register_extension(ex, true)
end
end # module AttributeHelper
end # module Trimble
#-------------------------------------------------------------------------------
file_loaded(__FILE__)
#-------------------------------------------------------------------------------
-285
View File
@@ -1,285 +0,0 @@
# Copyright 2014-2021, Trimble Inc.
#
# License: The MIT License (MIT)
require "sketchup.rb"
require "stringio"
module Trimble
module AttributeHelper
PLUGIN = self
class << self
attr_reader :app_observer
attr_reader :model_observer
attr_reader :selection_observer
end
def self.visualize_selected
content = self.traverse_selected
html = self.wrap_content(content)
options = {
:dialog_title => "Attribute Visualizer",
:preferences_key => 'AttributeVisualizer',
:scrollable => true,
:resizable => true,
:height => 300,
:width => 400,
:left => 200,
:top => 200
}
@window ||= UI::WebDialog.new(options)
@window.set_html(html)
@window.set_on_close {
@window = nil
self.detach_observers
}
unless @window.visible?
@window.show
self.attach_observers
end
end
def self.attach_observers
@app_observer ||= AppObserver.new
@model_observer ||= ModelObserver.new
@selection_observer ||= SelectionObserver.new
model = Sketchup.active_model
Sketchup.remove_observer(@app_observer)
model.remove_observer(@model_observer)
model.selection.remove_observer(@selection_observer)
Sketchup.add_observer(@app_observer)
model.add_observer(@model_observer)
model.selection.add_observer(@selection_observer)
end
def self.detach_observers
Sketchup.remove_observer(@app_observer)
Sketchup.active_model.remove_observer(@model_observer)
Sketchup.active_model.selection.remove_observer(@selection_observer)
end
def self.traverse_selected
html = StringIO.new
model = Sketchup.active_model
selection = model.selection
if selection.empty?
if model.active_path.nil?
entity = model
else
entity = model.active_path.last
end
else
return "Invalid selection size" unless selection.size == 1
entity = selection[0]
end
html.puts "<h1>#{self.escape_html(entity)}</h1>"
if entity.respond_to?(:name)
html.puts "<h2>#{self.escape_html(entity.name)}</h2>"
end
if entity.attribute_dictionaries
entity.attribute_dictionaries.each { |dictionary|
html.puts self.format_dictionary(dictionary)
}
else
html.puts "No dictionaries"
end
if entity.is_a?(Sketchup::Group)
definition = entity.entities.parent
elsif entity.is_a?(Sketchup::ComponentInstance)
definition = entity.definition
else
definition = nil
end
if definition && definition.attribute_dictionaries
html.puts "<h1>#{self.escape_html(definition)}</h1>"
html.puts "<h2>#{self.escape_html(definition.name)}</h2>"
definition.attribute_dictionaries.each { |dictionary|
html.puts self.format_dictionary(dictionary)
}
end
html.string
end
def self.format_dictionary(dictionary, path = "")
html_name = self.escape_html(dictionary.name)
path = "#{path}:#{html_name}"
html = StringIO.new
html.puts "<table>"
html.puts "<caption title='#{path}'>#{html_name}</caption>"
html.puts "<tbody>"
dictionary.each { |key, value|
html_key = self.escape_html(key)
html_value = self.escape_html(value)
node_path = "#{path}:#{html_key}"
html.puts "<tr title='#{node_path}'><td>#{html_key}</td><td>#{html_value}</td><td class='value_type'>#{value.class}</td></tr>"
}
if dictionary.attribute_dictionaries
dictionary.attribute_dictionaries.each { |sub_dic|
html.puts "<tr><td colspan='3' class='dictionary'>"
html.puts self.format_dictionary(sub_dic, path)
html.puts "</td></tr>"
}
end
html.puts "</tbody>"
html.puts "</table>"
html.string
end
def self.escape_html(data)
data.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
end
def self.wrap_content(content)
html = <<-EOT
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta charset="UTF-8">
<style>
html {
font-family: "Calibri", sans-serif;
font-size: 10pt;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.2em;
}
table {
width: 100%;
/*padding: 0.5em;*/
border: 1px solid #666;
}
caption {
font-weight: bold;
text-align: left;
/*border-bottom: 1px solid silver;*/
padding: 0.2em;
}
td {
background: #f3f3f3;
padding: 0.2em;
}
td.dictionary {
background: none;
padding-left: 1em;
}
tr:hover td {
background: rgba(255,210,180,0.2);
}
.value_type {
text-align: right;
width: 5%;
}
</style>
<head>
<body>
#{content}
</body>
</html>
EOT
end
class SelectionObserver < Sketchup::SelectionObserver
def onSelectionAdded(selection, element)
selection_changed()
end
def onSelectionBulkChange(selection)
selection_changed()
end
def onSelectionCleared(selection)
selection_changed()
end
def onSelectionRemoved(selection, element)
selection_changed()
end
private
def selection_changed
PLUGIN.visualize_selected
end
end # class SelectionObserver
class ModelObserver < Sketchup::ModelObserver
def onActivePathChanged(model)
PLUGIN.visualize_selected
end
def onTransactionCommit(model)
model_changed(model)
end
def onTransactionEmpty(model)
model_changed(model)
end
def onTransactionRedo(model)
model_changed(model)
end
def onTransactionUndo(model)
model_changed(model)
end
private
def model_changed(model)
if @timer.nil?
@timer = UI.start_timer(0.0, false) {
@timer = nil
PLUGIN.visualize_selected
}
end
end
end # class ModelObserver
class AppObserver < Sketchup::AppObserver
def onNewModel(model)
observe_model(model)
end
def onOpenModel(model)
observe_model(model)
end
private
def observe_model(model)
model.add_observer(PLUGIN.model_observer)
model.selection.add_observer(PLUGIN.selection_observer)
PLUGIN.visualize_selected
end
end # class AppObserver
unless file_loaded?(__FILE__)
command = UI::Command.new("Attribute Helper") { self.visualize_selected }
command.status_bar_text = "Inspect and edit the attributes of a selection."
menu_name = Sketchup.version.to_f < 21.1 ? 'Plugins' : 'Developer'
menu = UI.menu(menu_name)
menu.add_item(command)
file_loaded(__FILE__)
end
end # module AttributeHelper
end # module Sketchup
-2
View File
@@ -1,2 +0,0 @@
ID=f4d9d053-4479-4a9a-90da-b79fa16e28c4
VERSION_ID=b787af5e-8e8e-4932-92ef-a3c99681795d
Binary file not shown.
+2 -3
View File
@@ -25,8 +25,7 @@ def patch_installer(tag):
with open(iss_file, "r") as file:
lines = file.readlines()
lines.insert(11, f'#define AppVersion "{tag.split("-")[0]}"\n')
lines.insert(12, f'#define AppInfoVersion "{tag}"\n')
lines.insert(11, f'#define AppVersion "{tag}"\n')
with open(iss_file, "w") as file:
file.writelines(lines)
@@ -40,7 +39,7 @@ def main():
return
tag = sys.argv[1]
if not re.match(r"([0-9]+)\.([0-9]+)\.([0-9]+)", tag):
if not re.match(r"[0-9]+(\.[0-9]+)*$", tag):
raise ValueError(f"Invalid tag provided: {tag}")
print(f"Patching version: {tag}")
+33 -27
View File
@@ -1,39 +1,45 @@
# frozen_string_literal: true
require "sketchup"
require 'sketchup'
require 'extensions'
require "extensions"
# Speckle connector module to enable multiplayer mode ON!
module SpeckleConnector
# Version - patched by CI
CONNECTOR_VERSION = '0.0.0'
module SpeckleSystems
module SpeckleConnector
# Version - patched by CI
CONNECTOR_VERSION = "0.0.0"
file = __FILE__.dup
file = __FILE__.dup
# Account for Ruby encoding bug under Windows.
file.force_encoding("UTF-8") if file.respond_to?(:force_encoding)
# Support folder should be named the same as the root .rb file.
folder_name = File.basename(file, ".*")
# Account for Ruby encoding bug under Windows.
file.force_encoding('UTF-8') if file.respond_to?(:force_encoding)
# Path to the root .rb file (this file).
PATH_ROOT = File.dirname(file).freeze
# Support folder should be named the same as the root .rb file.
folder_name = File.basename(file, '.*')
# Path to the support folder.
PATH = File.join(PATH_ROOT, folder_name).freeze
# Path to the root .rb file (this file).
PATH_ROOT = File.dirname(file).freeze
# Run from localhost or from build files
DEV_MODE = false
puts("Loading Speckle Connector v#{CONNECTOR_VERSION} from #{DEV_MODE ? 'dev' : 'build'}")
# Path to the support folder.
PATH = File.join(PATH_ROOT, folder_name).freeze
# Run from localhost or from build files
DEV_MODE = false
puts("Loading Speckle Connector v#{CONNECTOR_VERSION} from #{DEV_MODE ? 'dev' : 'build'}")
unless file_loaded?(__FILE__)
unless file_loaded?(__FILE__)
ex = SketchupExtension.new('Speckle SketchUp', File.join(PATH, 'bootstrap'))
ex.description = 'Speckle Connector for SketchUp'
ex.version = CONNECTOR_VERSION
ex.copyright = 'AEC Systems Ltd.'
ex.creator = 'Speckle Systems'
Sketchup.register_extension(ex, true)
ex = SketchupExtension.new("Speckle SketchUp", File.join(PATH, "main"))
file_loaded(__FILE__)
ex.description = "Speckle Connector for SketchUp"
ex.version = CONNECTOR_VERSION
ex.copyright = "AEC Systems Ltd."
ex.creator = "Speckle Systems"
Sketchup.register_extension(ex, true)
file_loaded(__FILE__)
end
end
end
+57
View File
@@ -0,0 +1,57 @@
require "JSON"
begin
require("sqlite3")
rescue LoadError
# ty msp-greg! https://github.com/MSP-Greg/SUMisc/releases/tag/sqlite3-mingw-1
Gem.install(File.join(File.dirname(File.expand_path(__FILE__)), "utils/sqlite3-1.4.2.mspgreg-x64-mingw32.gem"))
require("sqlite3")
end
module SpeckleSystems::SpeckleConnector
module Accounts
def self.load_accounts
dir = _get_speckle_dir
db_path = File.join(dir, "Accounts.db")
unless File.exist?(db_path)
raise(IOError, "No Accounts db found. Please read the guide for different options for adding your account: \nhttps://speckle.guide/user/manager.html#adding-accounts")
end
db = SQLite3::Database.new(db_path)
rows = db.execute("SELECT * FROM objects")
db.close
rows.map { |row| JSON.parse(row[1]) }
end
def self.default_account
accts = load_accounts
accts.select { |acc| acc["isDefault"] }[0] || accts[0]
end
def self.get_suuid
dir = _get_speckle_dir
suuid_path = File.join(dir, "suuid")
return unless File.exist?(suuid_path)
File.read(suuid_path)
end
def self._get_speckle_dir
speckle_dir =
case Sketchup.platform
# sometimes Dir.home on windows points somewhere else bc I guess it's picking up a higher level user?
when :platform_win then File.join(Dir.pwd[%r{^((?:[^/]*/){3})}], "AppData/Roaming/Speckle")
when :platform_osx then File.join(Dir.home, ".config", "Speckle")
else
nil
end
return speckle_dir if Dir.exist?(speckle_dir)
raise(
IOError,
"No Speckle Directory exists. Please read the guide to get Speckle set up on your machine: \nhttps://speckle.guide/user/manager.html"
)
end
end
end
-37
View File
@@ -1,37 +0,0 @@
# frozen_string_literal: true
require 'sketchup'
require 'pathname'
require 'speckle_connector/debug'
require_relative 'src/log/log'
require_relative 'src/ui/sketchup_ui'
require_relative 'src/ui/ui_controller'
require_relative 'src/commands/menu_command_handler'
require_relative 'src/app/speckle_connector_app'
require_relative 'src/states/user_state'
require_relative 'src/states/initial_state'
require_relative 'src/commands/speckle_menu_commands'
# Speckle Connector on SketchUp to enable Multiplayer mode ON!
module SpeckleConnector
SKETCHUP_VERSION = Sketchup.version.to_i
dir = __dir__.dup
dir.force_encoding('UTF-8') if dir.respond_to?(:force_encoding)
SPECKLE_CONNECTOR_SRC_PATH = Pathname.new(File.expand_path(dir)).cleanpath.to_s
def self.initialize_app
sketchup_ui = Ui::SketchupUi.new
ui_controller = Ui::UiController.new(sketchup_ui)
menu_commands = Commands::MenuCommandHandler.new
user_state = SpeckleConnector::States::UserState.new({})
initial_state = SpeckleConnector::States::InitialState.new(user_state)
app = SpeckleConnector::App::SpeckleConnectorApp.new(menu_commands, initial_state, ui_controller)
# Add menu commands to SketchUp and Speckle application
Commands::SpeckleMenuCommands.add_initial_commands!(app)
app
end
app = initialize_app
SPECKLE_APP = app
end
@@ -0,0 +1,32 @@
require "sketchup"
require "speckle_connector/converter/to_speckle"
require "speckle_connector/converter/to_native"
module SpeckleSystems::SpeckleConnector
SKETCHUP_UNIT_STRINGS = { "m" => "m", "mm" => "mm", "ft" => "feet", "in" => "inch", "yd" => "yard", "cm" => "cm" }.freeze
public_constant :SKETCHUP_UNIT_STRINGS
class ConverterSketchup
include ToNative
include ToSpeckle
attr_accessor :units, :component_defs, :registry, :entity_observer
def initialize(units = "m")
@units = units
@component_defs = {}
# @registry = Sketchup.active_model.attribute_dictionary("speckle_id_registry", true)
# @entity_observer = SpeckleEntityObserver.new
end
def convert_to_speckle(obj)
case obj.typename
when "Edge" then edge_to_speckle(obj)
when "Face" then face_to_speckle(obj)
when "Group" then component_instance_to_speckle(obj, is_group: true)
when "ComponentDefinition" then component_definition_to_speckle(obj)
when "ComponentInstance" then component_instance_to_speckle(obj)
else nil
end
end
end
end
+21
View File
@@ -0,0 +1,21 @@
module SpeckleSystems::SpeckleConnector
class SpeckleEntityObserver < Sketchup::EntityObserver
attr_accessor :registry
def initialize
super()
@registry = Sketchup.active_model.attribute_dictionary("speckle_id_registry", true)
end
def onEraseEntity(entity)
app_id = entity.get_attribute("speckle", "applicationId")
return if app_id.nil?
p(app_id)
@registry.delete_key(app_id)
p(@registry)
end
end
end
+243
View File
@@ -0,0 +1,243 @@
require "sketchup"
# To Native conversions for the ConverterSketchup
module SpeckleSystems::SpeckleConnector::ToNative
def traverse_commit_object(obj)
if can_convert_to_native(obj)
convert_to_native(obj, Sketchup.active_model.entities)
elsif obj.is_a?(Hash) && obj.key?("speckle_type")
return if is_ignored_speckle_type(obj)
puts(">>> Found #{obj["speckle_type"]}: #{obj["id"]}")
props = obj.keys.filter_map { |key| key unless key.start_with?("_") }
props.each { |prop| traverse_commit_object(obj[prop]) }
elsif obj.is_a?(Hash)
obj.each_value { |value| traverse_commit_object(value) }
elsif obj.is_a?(Array)
obj.each { |value| traverse_commit_object(value) }
else
nil
end
end
def can_convert_to_native(obj)
return false unless obj.is_a?(Hash) && obj.key?("speckle_type")
[
"Objects.Geometry.Line",
"Objects.Geometry.Polyline",
"Objects.Geometry.Mesh",
"Objects.Geometry.Brep",
"Objects.Other.BlockInstance",
"Objects.Other.BlockDefinition",
"Objects.Other.RenderMaterial"
].include?(obj["speckle_type"])
end
def is_ignored_speckle_type(obj)
["Objects.BuiltElements.Revit.Parameter"].include?(obj["speckle_type"])
end
def convert_to_native(obj, entities = Sketchup.active_model.entities)
puts(">>> Converting #{obj["speckle_type"]}: #{obj["id"]}")
case obj["speckle_type"]
when "Objects.Geometry.Line", "Objects.Geometry.Polyline" then if entities == Sketchup.active_model.entities
edge_to_native_component(obj, entities)
else
edge_to_native(obj, entities)
end
when "Objects.Other.BlockInstance" then component_instance_to_native(obj, entities)
when "Objects.Other.BlockDefinition" then component_definition_to_native(obj)
when "Objects.Geometry.Mesh" then if entities == Sketchup.active_model.entities
mesh_to_native_component(obj, entities)
else
mesh_to_native(obj, entities)
end
when "Objects.Geometry.Brep" then if entities == Sketchup.active_model.entities
mesh_to_native_component(obj["displayMesh"], entities)
else
mesh_to_native(obj["displayMesh"], entities)
end
when obj.key?["displayValue"]
parent_id = obj["applicationId"] || obj["id"]
obj["displayValue"].each do |o|
o["applicationId"] = "#{parent_id}::#{o["id"]}" if o["applicationId"].nil?
convert_to_native(o, entities)
end
else
nil
end
rescue StandardError => e
puts("Failed to convert #{obj["speckle_type"]} (id: #{obj["id"]})")
puts(e)
nil
end
# def register_receive(entity, id)
# # entity.add_observer(@entity_observer)
# # entity.set_attribute("speckle", "applicationId", id)
# @registry[id] = entity.persistent_id
# end
# def get_received_entity(app_id)
# return if @registry[app_id].nil?
# end
# def received?(id)
# !@registry[id].nil?
# end
def length_to_native(length, units = @units)
length.__send__(SpeckleSystems::SpeckleConnector::SKETCHUP_UNIT_STRINGS[units])
end
def edge_to_native(line, entities)
if line.key?("value")
values = line["value"]
points = values.each_slice(3).to_a.map { |pt| point_to_native(pt[0], pt[1], pt[2], line["units"]) }
points.push(points[0]) if line["closed"]
entities.add_edges(*points)
else
start_pt = point_to_native(line["start"]["x"], line["start"]["y"], line["start"]["z"], line["units"])
end_pt = point_to_native(line["end"]["x"], line["end"]["y"], line["end"]["z"], line["units"])
entities.add_edges(start_pt, end_pt)
end
end
def edge_to_native_component(line, entities)
line_id = line["applicationId"] || line["id"]
definition = component_definition_to_native([line], "def::#{line_id}")
find_and_erase_existing_instance(definition, line_id)
instance = entities.add_instance(definition, Geom::Transformation.new)
instance.name = line_id
instance
end
def face_to_native
nil
end
def point_to_native(x, y, z, units)
Geom::Point3d.new(length_to_native(x, units), length_to_native(y, units), length_to_native(z, units))
end
# converts a mesh to a native mesh and adds the faces to the given entities collection
def mesh_to_native(mesh, entities)
native_mesh = Geom::PolygonMesh.new(mesh["vertices"].count / 3)
points = []
mesh["vertices"].each_slice(3) do |pt|
points.push(point_to_native(pt[0], pt[1], pt[2], mesh["units"]))
end
faces = mesh["faces"]
while faces.count.positive?
num_pts = faces.shift
# 0 -> 3, 1 -> 4 to preserve backwards compatibility
num_pts += 3 if num_pts < 3
indices = faces.shift(num_pts)
native_mesh.add_polygon(indices.map { |index| points[index] })
end
entities.add_faces_from_mesh(native_mesh, 4, material_to_native(mesh["renderMaterial"]))
native_mesh
end
# creates a component definition and instance from a mesh
def mesh_to_native_component(mesh, entities)
mesh_id = mesh["applicationId"] || mesh["id"]
definition = component_definition_to_native([mesh], "def::#{mesh_id}")
find_and_erase_existing_instance(definition, mesh_id)
instance = entities.add_instance(definition, Geom::Transformation.new)
instance.name = mesh_id
instance.material = material_to_native(mesh["renderMaterial"])
instance
end
# finds or creates a component definition from the geometry and the given name
def component_definition_to_native(geometry, name, application_id = "")
definition = Sketchup.active_model.definitions[name]
return definition if definition && (definition.name == name || definition.guid == application_id)
definition&.entities&.clear!
definition ||= Sketchup.active_model.definitions.add(name)
geometry.each { |obj| convert_to_native(obj, definition.entities) }
puts("definition finished: #{name} (#{application_id})")
puts(" entity count: #{definition.entities.count}")
definition
end
# takes a component definition and finds and erases the first instance with the matching name (and optionally the applicationId)
def find_and_erase_existing_instance(definition, name, app_id = "")
definition.instances.find { |ins| ins.name == name || ins.guid == app_id }&.erase!
end
# creates a component instance from a block
def component_instance_to_native(block, entities)
# is_group = block.key?("is_sketchup_group") && block["is_sketchup_group"]
# something about this conversion is freaking out if nested block geo is a group
# so this is set to false always until I can figure this out
is_group = false
definition = component_definition_to_native(
block["blockDefinition"]["geometry"],
block["blockDefinition"]["name"],
block["blockDefinition"]["applicationId"]
)
name = block["name"].nil? || block["name"].empty? ? block["id"] : block["name"]
find_and_erase_existing_instance(definition, name, block["applicationId"])
transform = transform_to_native(
block["transform"].is_a?(Hash) ? block["transform"]["value"] : block["transform"],
block["units"]
)
instance =
if is_group
entities.add_group(definition.entities.to_a)
else
entities.add_instance(definition, transform)
end
puts("Failed to create instance for speckle block instance #{block["id"]}") if instance.nil?
instance.transformation = transform if is_group
instance.material = material_to_native(block["renderMaterial"])
instance.name = name
instance
end
def transform_to_native(t_arr, units = @units)
Geom::Transformation.new(
[
t_arr[0],
t_arr[4],
t_arr[8],
t_arr[12],
t_arr[1],
t_arr[5],
t_arr[9],
t_arr[13],
t_arr[2],
t_arr[6],
t_arr[10],
t_arr[14],
length_to_native(t_arr[3], units),
length_to_native(t_arr[7], units),
length_to_native(t_arr[11], units),
t_arr[15]
]
)
end
def material_to_native(render_mat)
return if render_mat.nil?
# return material with same name if it exists
name = render_mat["name"] || render_mat["id"]
material = Sketchup.active_model.materials[name]
return material if material
# create a new sketchup material
material = Sketchup.active_model.materials.add(name)
material.alpha = render_mat["opacity"]
argb = render_mat["diffuse"]
material.color = Sketchup::Color.new((argb >> 16) & 255, (argb >> 8) & 255, argb & 255, (argb >> 24) & 255)
material
end
end
+270
View File
@@ -0,0 +1,270 @@
require "sketchup"
# To Speckle conversions for the ConverterSketchup
module SpeckleSystems::SpeckleConnector::ToSpeckle
def length_to_speckle(length)
length.__send__("to_#{SpeckleSystems::SpeckleConnector::SKETCHUP_UNIT_STRINGS[@units]}")
end
# convert an edge to a speckle line
def edge_to_speckle(edge)
{
speckle_type: "Objects.Geometry.Line",
applicationId: edge.persistent_id.to_s,
units: @units,
start: vertex_to_speckle(edge.start),
end: vertex_to_speckle(edge.end),
domain: speckle_interval(0, Float(edge.length)),
bbox: bounds_to_speckle(edge.bounds)
}
end
# covnert a component definition to a speckle block definition
def component_definition_to_speckle(definition)
guid = definition.guid
return @component_defs[guid] if @component_defs.key?(guid)
speckle_def = {
speckle_type: "Objects.Other.BlockDefinition",
applicationId: guid,
units: @units,
name: definition.name,
# i think the base point is always the origin?
basePoint: speckle_point,
"@geometry" => if %w[Edge Face].include?(definition.entities[0].typename)
group_mesh_to_speckle(definition)
else
definition.entities.map { |entity| convert_to_speckle(entity) }
end
}
@component_defs[guid] = speckle_def
end
# convert a component instane to a speckle block instance
def component_instance_to_speckle(instance, is_group: false)
transform = instance.transformation
{
speckle_type: "Objects.Other.BlockInstance",
applicationId: instance.guid,
is_sketchup_group: is_group,
units: @units,
bbox: bounds_to_speckle(instance.bounds),
name: instance.name == "" ? nil : instance.name,
renderMaterial: instance.material.nil? ? nil : material_to_speckle(instance.material),
transform: transform_to_speckle(transform),
"@blockDefinition" => component_definition_to_speckle(instance.definition)
}
end
def group_mesh_to_speckle(component_def)
mat_groups = {}
nested_blocks = []
lines = []
component_def.entities.each do |entity|
nested_blocks.push(component_instance_to_speckle(entity)) if entity.typename == "ComponentInstance"
next unless %w[Edge Face].include?(entity.typename)
if entity.typename == "Edge"
lines.push(edge_to_speckle(entity))
else
face = entity
# convert material
mat_id = face.material.nil? ? "none" : face.material.entityID
mat_groups[mat_id] = initialise_group_mesh(face, component_def.bounds) unless mat_groups.key?(mat_id)
if face.loops.size > 1
mesh = face.mesh
mat_groups[mat_id]["@(31250)vertices"].push(*mesh_points_to_array(mesh))
mat_groups[mat_id]["@(62500)faces"].push(*mesh_faces_to_array(mesh, mat_groups[mat_id][:pt_count] - 1))
else
mat_groups[mat_id]["@(31250)vertices"].push(*face_vertices_to_array(face))
mat_groups[mat_id]["@(62500)faces"].push(*face_indices_to_array(face, mat_groups[mat_id][:pt_count]))
end
mat_groups[mat_id][:pt_count] += face.vertices.count
end
end
mat_groups.values.map { |group| group.delete(:pt_count) }
mat_groups.values + lines + nested_blocks
end
def transform_to_speckle(transform)
t_arr = transform.to_a
{
speckle_type: "Objects.Other.Transform",
units: @units,
value: [
t_arr[0],
t_arr[4],
t_arr[8],
length_to_speckle(t_arr[12]),
t_arr[1],
t_arr[5],
t_arr[9],
length_to_speckle(t_arr[13]),
t_arr[2],
t_arr[6],
t_arr[10],
length_to_speckle(t_arr[14]),
t_arr[3],
t_arr[7],
t_arr[11],
t_arr[15]
]
}
end
def initialise_group_mesh(face, bounds)
{
speckle_type: "Objects.Geometry.Mesh",
units: @units,
bbox: bounds_to_speckle(bounds),
"@(31250)vertices" => [],
"@(62500)faces" => [],
"@(31250)textureCoordinates" => [],
pt_count: 0,
renderMaterial: face.material.nil? ? nil : material_to_speckle(face.material)
}
end
# get an array of face indices from a sketchup polygon mesh
def mesh_faces_to_array(mesh, offset)
faces = []
puts(faces)
mesh.polygons.each do |poly|
faces.push(
poly.count, *poly.map { |index| index.abs + offset }
)
end
faces
end
# get a flat array of vertices from a sketchup polygon mesh
def mesh_points_to_array(mesh)
pts_array = []
mesh.points.each do |pt|
pts_array.push(
length_to_speckle(pt[0]),
length_to_speckle(pt[1]),
length_to_speckle(pt[2])
)
end
pts_array
end
# get a flat array of face indices from a sketchup face
def face_indices_to_array(face, offset)
face_array = [face.vertices.count]
face_array.push(*face.vertices.count.times.map { |index| index + offset })
face_array
end
# get a flat array of vertices from a list of sketchup vertices
def face_vertices_to_array(face)
pts_array = []
face.vertices.each do |v|
pt = v.position
pts_array.push(length_to_speckle(pt[0]), length_to_speckle(pt[1]), length_to_speckle(pt[2]))
end
pts_array
end
def uvs_to_array(mesh)
uvs_array = []
mesh.uvs(true).each do |pt|
uvs_array.push(
length_to_speckle(pt[0] / pt[2]),
length_to_speckle(pt[1] / pt[2])
)
end
uvs_array
end
def face_to_speckle(face)
mesh = face.loops.count > 1 ? face.mesh : nil
{
speckle_type: "Objects.Geometry.Mesh",
units: @units,
renderMaterial: face.material.nil? ? nil : material_to_speckle(face.material),
bbox: bounds_to_speckle(face.bounds),
"@(31250)vertices" => mesh.nil? ? face_vertices_to_array(face) : mesh_points_to_array(mesh),
"@(62500)faces" => mesh.nil? ? face_indices_to_array(face, 0) : mesh_faces_to_array(mesh, -1)
}
end
def vertex_to_speckle(vertex)
point = vertex.position
{
speckle_type: "Objects.Geometry.Point",
units: @units,
x: length_to_speckle(point[0]),
y: length_to_speckle(point[1]),
z: length_to_speckle(point[2])
}
end
def material_to_speckle(material)
rgba = material.color.to_a
{
speckle_type: "Objects.Other.RenderMaterial",
name: material.name,
diffuse: [rgba[3] << 24 | rgba[0] << 16 | rgba[1] << 8 | rgba[2]].pack("l").unpack1("l"),
opacity: material.alpha,
emissive: -16_777_216,
metalness: 0,
roughness: 1
}
end
def bounds_to_speckle(bounds)
min_pt = bounds.min
{
speckle_type: "Objects.Geometry.Box",
units: @units,
area: 0,
volume: 0,
xSize: speckle_interval(min_pt[0], bounds.width),
ySize: speckle_interval(min_pt[1], bounds.height),
zSize: speckle_interval(min_pt[2], bounds.depth),
basePlane: speckle_plane
}
end
def speckle_interval(start_val, end_val)
{
speckle_type: "Objects.Primitive.Interval",
units: @units,
start: start_val.is_a?(Length) ? length_to_speckle(start_val) : start_val,
end: end_val.is_a?(Length) ? length_to_speckle(end_val) : end_val
}
end
def speckle_point(x = 0.0, y = 0.0, z = 0.0, vector: false)
{
speckle_type: vector ? "Objects.Geometry.Vector" : "Objects.Geometry.Point",
units: @units,
x: x.is_a?(Length) ? length_to_speckle(x) : x,
y: y.is_a?(Length) ? length_to_speckle(y) : y,
z: z.is_a?(Length) ? length_to_speckle(z) : z
}
end
def speckle_vector(x = 0.0, y = 0.0, z = 0.0)
speckle_point(x, y, z, vector: true)
end
def speckle_plane(xdir: [1, 0, 0], ydir: [0, 1, 0], normal: [0, 0, 1], origin: [0, 0, 0])
{
speckle_type: "Objects.Geometry.Plane",
units: @units,
xdir: speckle_vector(*xdir),
ydir: speckle_vector(*ydir),
normal: speckle_vector(*normal),
origin: speckle_point(*origin)
}
end
end
+4 -11
View File
@@ -1,24 +1,17 @@
# frozen_string_literal: true
# Speckle connector module to enable multiplayer mode ON!
module SpeckleConnector
module SpeckleSystems::SpeckleConnector
# from thomthom
# https://github.com/thomthom/true-bend/blob/master/src/tt_truebend/debug.rb
# @note Debug method to reload the plugin.
#
# @example
# SpeckleConnector.reload
# SpeckleSystems::SpeckleConnector.reload
#
# @return [Integer] Number of files reloaded.
# rubocop:disable SketchupSuggestions/FileEncoding
def self.reload
load(__FILE__)
pattern = File.join(__dir__, '**/*.rb')
# TODO: Here is a opportunity to improve reloading process.
# We can cache last edited time of the each file later to check which file need to be reloaded.
Dir.glob(pattern).each { |file| load(file) unless file.include?('bootstrap') }
pattern = File.join(__dir__, "**/*.rb")
Dir.glob(pattern).each { |file| load(file) }
.size
end
# rubocop:enable SketchupSuggestions/FileEncoding
end
+204
View File
@@ -0,0 +1,204 @@
require "JSON"
require "json"
require "sketchup"
require "speckle_connector/converter/converter_sketchup"
require "speckle_connector/accounts"
module SpeckleSystems::SpeckleConnector
UNITS = { 0 => "in", 1 => "ft", 2 => "mm", 3 => "cm", 4 => "m", 5 => "yd" }.freeze
public_constant :UNITS
@to_send = {}
@connected = false
def self.queue_send(stream_id, converted)
@to_send = { stream_id: stream_id, converted: converted }
send_from_queue(stream_id) if @connected
end
def self.send_from_queue(stream_id)
return unless @to_send[:stream_id] == stream_id
@dialog.execute_script("convertedFromSketchup('#{@to_send[:stream_id]}',#{@to_send[:converted].to_json})")
@dialog.execute_script("oneClickSend('#{@to_send[:stream_id]}')")
@to_send = {}
end
def self.init_dialog
options = {
dialog_title: "SpeckleSketchUp",
preferences_key: "example.htmldialog.materialinspector",
style: UI::HtmlDialog::STYLE_DIALOG,
min_width: 250,
min_height: 50
}
dialog = UI::HtmlDialog.new(options)
dialog.center
dialog
end
def self.create_dialog(show: true)
if @dialog&.visible?
@dialog.bring_to_front
else
@dialog ||= init_dialog
@dialog.add_action_callback("send_selection") do |_action_context, stream_id|
send_selection(stream_id)
nil
end
@dialog.add_action_callback("receive_objects") do |_action_context, base, stream_id|
receive_objects(base, stream_id)
nil
end
@dialog.add_action_callback("reload_accounts") do |_action_context|
reload_accounts
end
@dialog.add_action_callback("init_local_accounts") do |_action_context|
init_local_accounts
end
@dialog.add_action_callback("load_saved_streams") do |_action_context|
load_saved_streams
end
@dialog.add_action_callback("save_stream") do |_action_context, stream_id|
save_stream(stream_id)
end
@dialog.add_action_callback("remove_stream") do |_action_context, stream_id|
remove_stream(stream_id)
end
@dialog.add_action_callback("notify_connected") do |_action_context, stream_id|
notify_connected(stream_id)
end
# set connected to false when dialog is closed
@dialog.set_can_close do
@connected = false
!@connected
end
if DEV_MODE
puts("Launching Speckle Connector from http://localhost:8081")
@dialog.set_url("http://localhost:8081")
else
html_file = File.join(File.dirname(File.expand_path(__FILE__)), "html", "index.html")
puts("Launching Speckle Connector from #{html_file}")
@dialog.set_file(html_file)
end
@dialog.show if show
end
@dialog
end
def self.notify_connected(stream_id)
@connected = true
send_from_queue(stream_id)
end
def self.convert_to_speckle(to_convert)
converter = ConverterSketchup.new(UNITS[Sketchup.active_model.options["UnitsOptions"]["LengthUnit"]])
to_convert.map { |entity| converter.convert_to_speckle(entity) }
end
def self.send_selection(stream_id)
converted = convert_to_speckle(Sketchup.active_model.selection)
puts("converted #{converted.count} objects for stream #{stream_id}")
# puts(converted.to_json)
@dialog.execute_script("convertedFromSketchup('#{stream_id}',#{converted.to_json})")
rescue StandardError => e
puts(e)
@dialog.execute_script("sketchupOperationFailed('#{stream_id}')")
end
def self.receive_objects(base, stream_id)
puts("received objects from stream #{stream_id}")
model = Sketchup.active_model
converter = ConverterSketchup.new(UNITS[model.options["UnitsOptions"]["LengthUnit"]])
converter.traverse_commit_object(base)
@dialog.execute_script("finishedReceiveInSketchup('#{stream_id}')")
rescue StandardError => e
puts(e)
@dialog.execute_script("sketchupOperationFailed('#{stream_id}')")
end
def self.one_click_send
acct = Accounts.default_account
return puts("No local account found. Please refer to speckle.guide for more information.") if acct.nil?
create_dialog
to_convert = Sketchup.active_model.selection.count > 0 ? Sketchup.active_model.selection : Sketchup.active_model.entities
if first_saved_stream.nil?
create_stream(to_convert)
else
queue_send(first_saved_stream, convert_to_speckle(to_convert))
end
rescue StandardError => e
puts(e)
@dialog.execute_script("sketchupOperationFailed('#{@to_send[:stream_id]}')")
end
def self.first_saved_stream
saved_streams = Sketchup.active_model.attribute_dictionary("speckle", true)["streams"] or []
saved_streams.nil? || saved_streams.empty? ? nil : saved_streams[0]
end
def self.load_saved_streams
saved_streams = Sketchup.active_model.attribute_dictionary("speckle", true)["streams"] or []
@dialog.execute_script("setSavedStreams(#{saved_streams})")
end
def self.init_local_accounts
puts("Initialisation of Speckle accounts requested by plugin")
@dialog.execute_script("loadAccounts(#{Accounts.load_accounts.to_json}, #{Accounts.get_suuid.to_json})")
end
def self.reload_accounts
puts("Reload of Speckle accounts requested by plugin")
@dialog.execute_script("loadAccounts(#{Accounts.load_accounts.to_json})")
load_saved_streams
end
def self.save_stream(stream_id)
speckle_dict = Sketchup.active_model.attribute_dictionary("speckle", true)
saved = speckle_dict["streams"] || []
saved = saved.empty? ? [stream_id] : saved.unshift(stream_id)
speckle_dict["streams"] = saved
load_saved_streams
end
def self.remove_stream(stream_id)
speckle_dict = Sketchup.active_model.attribute_dictionary("speckle", true)
saved = speckle_dict["streams"] || []
saved -= [stream_id]
speckle_dict["streams"] = saved
load_saved_streams
end
def self.create_stream(to_convert)
acct = Accounts.default_account
return if acct.nil?
path = Sketchup.active_model.path
stream_name = path ? File.basename(path, ".*") : "Untitled SketchUp Model"
query = "mutation streamCreate($stream: StreamCreateInput!) {streamCreate(stream: $stream)}"
vars = { stream: { name: stream_name, description: "Stream created from SketchUp" } }
request = Sketchup::Http::Request.new("#{acct["serverInfo"]["url"]}/graphql", Sketchup::Http::POST)
request.headers = { "Authorization" => "Bearer #{acct["token"]}", "Content-Type" => "application/json" }
request.body = { query: query, variables: vars }.to_json
request.start do |_req, res|
res_data = JSON.parse(res.body)["data"]
raise(StandardError) unless res_data
stream_id = res_data["streamCreate"]
save_stream(stream_id)
queue_send(stream_id, convert_to_speckle(to_convert))
# send_selection(stream_id)
end
load_saved_streams
rescue StandardError => e
puts(e)
puts("Could not create a new stream")
end
end

Before

Width:  |  Height:  |  Size: 798 B

After

Width:  |  Height:  |  Size: 798 B

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 665 B

After

Width:  |  Height:  |  Size: 665 B

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 639 B

After

Width:  |  Height:  |  Size: 639 B

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

+37
View File
@@ -0,0 +1,37 @@
# frozen_string_literal: true
require "sketchup"
require "speckle_connector/dialog"
require "speckle_connector/debug"
module SpeckleSystems
module SpeckleConnector
unless file_loaded?(__FILE__)
cmd_cube = UI::Command.new("Dialog") { create_dialog }
cmd_cube.tooltip = "Launch Connector"
cmd_cube.status_bar_text = "Opens the Speckle Connector window"
cmd_cube.small_icon = "icons/s2logo.png"
cmd_cube.large_icon = "icons/s2logo.png"
menu = UI.menu("Tools")
menu.add_item(cmd_cube)
cmd_send = UI::Command.new("Send") { one_click_send }
cmd_send.tooltip = "Send to Speckle"
cmd_send.status_bar_text = "Send to Speckle"
cmd_send.small_icon = "icons/Sender.png"
cmd_send.large_icon = "icons/Sender.png"
menu = UI.menu("Tools")
menu.add_item(cmd_send)
toolbar = UI::Toolbar.new("Speckle")
toolbar.add_item(cmd_cube)
toolbar.add_item(cmd_send)
toolbar.restore
file_loaded(__FILE__)
end
end
end
@@ -1,31 +0,0 @@
# frozen_string_literal: true
require 'JSON'
require_relative '../ext/sqlite3'
require_relative '../constants/path_constants'
module SpeckleConnector
# Accounts to communicate with models on user's account.
module Accounts
def self.load_accounts
db_path = SPECKLE_ACCOUNTS_DB_PATH
unless File.exist?(db_path)
raise(
IOError,
"No Accounts db found. Please read the guide for different options for adding your account:\n
https://speckle.guide/user/manager.html#adding-accounts"
)
end
db = Sqlite3::Database.new(db_path)
rows = db.exec('SELECT * FROM objects')
db.close
rows.map { |row| JSON.parse(row[1]) }
end
def self.default_account
accounts = load_accounts
accounts.select { |acc| acc['isDefault'] }[0] || accounts[0]
end
end
end
-15
View File
@@ -1,15 +0,0 @@
# frozen_string_literal: true
module SpeckleConnector
module Actions
# State changer object.
class Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @param parameters [Array] parameters that the action takes
# @return [States::State] the new updated state object
def self.update_state(_state, *_parameters)
raise NotImplementedError, 'Implement in subclass.'
end
end
end
end
@@ -1,33 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'deactivate_diffing'
module SpeckleConnector
module Actions
# Deactivate diffing for stream.
class ActivateDiffing < Action
def initialize(stream_id)
super()
@stream_id = stream_id
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
state = DeactivateDiffing.update_state(state, {})
puts "Diffing activated for #{@stream_id}"
speckle_entities = state.speckle_state.speckle_entities
invalid_speckle_entities = speckle_entities.select do |_id, entity|
entity.invalid_stream_ids.include?(@stream_id) && entity.sketchup_entity.is_a?(Sketchup::Face)
end
invalid_speckle_entities.each do |id, entity|
new_entity = entity.activate_diffing(@stream_id, state.sketchup_state.materials.by_id(MAT_EDIT))
speckle_entities = speckle_entities.put(id, new_entity)
end
new_speckle_state = state.speckle_state.with_speckle_entities(speckle_entities)
state.with_speckle_state(new_speckle_state)
end
end
end
end
@@ -1,52 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Adds material to speckle state and Sketchup.
class AddMaterial < Action
def self.update_state(state, material_name:, color:, material_id:, alpha: nil)
materials = state.sketchup_state.materials
existing_material = materials.by_id(material_id)
return state if existing_material&.valid?
new_material = create_or_get_material(state.sketchup_state.sketchup_model,
material_name,
color,
material_id,
alpha: alpha)
new_materials = materials.add_material(material_id, new_material)
new_sketchup_state = state.sketchup_state.with(:@materials => new_materials)
state.with(:@sketchup_state => new_sketchup_state)
end
def self.create_or_get_material(model, material_name, color, material_id, alpha: nil)
materials = model.materials
existing_material = materials.find { |mat| mat.name == material_name }
return existing_material if existing_material&.valid?
existing_material = materials.add material_name
existing_material.set_attribute(MAT_DICTIONARY, MAT_ID, material_id.to_s)
set_hex_color(existing_material, color)
existing_material.alpha = alpha if alpha
existing_material
end
def self.set_hex_color(skp_material, hex_value)
hex_value = hex_value.to_s
col_blue, col_green, col_red = parse_hex_color(hex_value)
skp_material.color = col_red, col_green, col_blue
end
def self.parse_hex_color(hex_value)
split_values = hex_value.match(/^#([a-fA-F\d]{2})([a-fA-F\d]{2})([a-fA-F\d]{2})$/) ||
hex_value.match(/^#([a-fA-F\d])([a-fA-F\d])([a-fA-F\d])$/)
col_red = split_values[1].hex
col_green = split_values[2].hex
col_blue = split_values[3].hex
return col_blue, col_green, col_red
end
end
end
end
@@ -1,73 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'mapped_entities_updated'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Apply mappings for selected entities.
class ApplyMappings < Action
def initialize(entities_to_map, method, category, family,
family_type, level, name, is_definition)
super()
@entities_to_map = entities_to_map
@method = method
@category = category
@name = name
@family = family
@family_type = family_type
@level = level
@is_definition = is_definition
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/MethodLength
def update_state(state)
sketchup_model = state.sketchup_state.sketchup_model
entities = if sketchup_model.active_path.nil?
sketchup_model.entities
else
sketchup_model.active_path.last.definition.entities
end
# Collect entities from entity ids that comes from UI as list
entities_to_map = entities.select { |e| @entities_to_map.include?(e.persistent_id) }
# Switch to definitions if all entities are component instance and UI flag shows that
if entities_to_map.all? { |e| e.is_a?(Sketchup::ComponentInstance) } && @is_definition
entities_to_map = entities_to_map.collect(&:definition).uniq
end
# Store speckle state to update with mapped entities.
speckle_state = state.speckle_state
entities_to_map.each do |entity|
name = if @name == '<Mixed>'
entity.respond_to?(:name) ? entity.name : ''
else
@name
end
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.set_attribute(entity, :category, @category)
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.set_attribute(entity, :name, name)
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.set_attribute(entity, :method, @method)
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.set_attribute(entity, :family, @family)
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.set_attribute(entity, :family_type, @family_type)
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.set_attribute(entity, :level, @level)
speckle_state = speckle_state.with_mapped_entity(entity)
end
new_state = MappedEntitiesUpdated.update_state(state.with_speckle_state(speckle_state))
Events::SelectionEventAction.update_state(new_state, { apply: true })
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/MethodLength
end
end
end
@@ -1,31 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
module SpeckleConnector
module Actions
# Clear mapper source.
class ClearMapperSource < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
new_speckle_state = state.speckle_state.with_removed_mapper_source
erase_levels(state)
state.with_speckle_state(new_speckle_state)
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
def self.erase_levels(state)
levels = state.sketchup_state.sketchup_model.definitions.select do |definition|
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.get_attribute(definition, :speckle_type) ==
OBJECTS_BUILTELEMENTS_REVIT_LEVEL
end
levels.each do |level|
level.entities.clear!
level.instances.each(&:erase!)
end
end
end
end
end
@@ -1,52 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'mapped_entities_updated'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Clear mappings for selected entities.
class ClearMappings < Action
def initialize(entities_to_map, is_definition)
super()
@entities_to_map = entities_to_map
@is_definition = is_definition
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def update_state(state)
sketchup_model = state.sketchup_state.sketchup_model
entities = if sketchup_model.active_path.nil?
sketchup_model.entities
else
sketchup_model.active_path.last.definition.entities
end
# Collect entities from entity ids that comes from UI as list
entities_to_map = entities.select { |e| @entities_to_map.include?(e.persistent_id) }
# Switch to definitions if all entities are component instance and UI flag shows that
if entities_to_map.all? { |e| e.is_a?(Sketchup::ComponentInstance) } && @is_definition
entities_to_map = entities_to_map.collect(&:definition).uniq
end
# Store speckle state to update with mapped entities.
speckle_state = state.speckle_state
entities_to_map.each do |entity|
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.remove_dictionary(entity)
speckle_state = speckle_state.with_removed_mapped_entity(entity)
end
new_state = MappedEntitiesUpdated.update_state(state.with_speckle_state(speckle_state))
Events::SelectionEventAction.update_state(new_state, { clear: true })
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
end
end
end
@@ -1,34 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'mapped_entities_updated'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Clear mappings for selected entities from mapped elements table.
class ClearMappingsFromTable < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, data)
# Flat entities to clear mappings
flat_entities = SketchupModel::Query::Entity.flat_entities(state.sketchup_state.sketchup_model.entities)
# Collect entity ids to clear mappings
entity_ids = data.collect { |_, entities| entities['selectedElements'].collect { |e| e['entityId'] } }.flatten
# Store speckle state to update with mapped entities.
speckle_state = state.speckle_state
flat_entities.each do |entity|
next unless entity_ids.include?(entity.persistent_id)
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.remove_dictionary(entity)
speckle_state = speckle_state.with_removed_mapped_entity(entity)
end
new_state = MappedEntitiesUpdated.update_state(state.with_speckle_state(speckle_state))
Events::SelectionEventAction.update_state(new_state, { clear: true })
end
end
end
end
@@ -1,17 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Clear queue from state.
class ClearQueue < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state)
new_speckle_state = state.speckle_state.with(:@message_queue => {})
state.with(:@speckle_state => new_speckle_state)
end
end
end
end
@@ -1,18 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../ext/sqlite3'
require_relative '../constants/path_constants'
module SpeckleConnector
module Actions
# Action to collect preferences from database to UI.
class CollectPreferences < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
state.with_add_queue('collectPreferences', state.user_state.preferences.to_json, [])
end
end
end
end
@@ -1,20 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Action to collect versions from sketchup and connector to track user's version by mixpanel.
class CollectVersions < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
versions = {
sketchup: Sketchup.version.to_i,
speckle: SpeckleConnector::CONNECTOR_VERSION
}
state.with_add_queue('collectVersions', versions.to_json, [])
end
end
end
end
@@ -1,15 +0,0 @@
# frozen_string_literal: true
module SpeckleConnector
module Actions
# Action to update connected state of application.
class Connected < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state)
puts 'Speckle connected!'
state.with(:@connected => true)
end
end
end
end
@@ -1,66 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../accounts/accounts'
require_relative '../actions/save_stream'
require_relative '../actions/queue_send'
require_relative '../convertors/converter'
module SpeckleConnector
module Actions
# Create stream.
class CreateStream < Action
def initialize(stream_name: nil)
super()
@stream_name = stream_name
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
# rubocop:disable Metrics/MethodLength
def update_state(state)
puts 'send to speckle'
acct = Accounts.default_account
if acct.nil?
puts 'No local account found. Please refer to speckle.guide for more information.'
return state
end
sketchup_model = state.sketchup_state.sketchup_model
path = sketchup_model.path
if @stream_name.nil?
@stream_name = path ? File.basename(path, '.*') : 'Untitled SketchUp Model'
end
query = 'mutation streamCreate($stream: StreamCreateInput!) {streamCreate(stream: $stream)}'
vars = { stream: { name: @stream_name, description: 'Stream created from SketchUp' } }
request = Sketchup::Http::Request.new("#{acct['serverInfo']['url']}/graphql", Sketchup::Http::POST)
request.headers = { 'Authorization' => "Bearer #{acct['token']}", 'Content-Type' => 'application/json' }
request.body = { query: query, variables: vars }.to_json
to_convert = if sketchup_model.selection.count > 0
sketchup_model.selection
else
sketchup_model.entities
end
state = evaluate_request(sketchup_model, request, state, to_convert)
Actions::LoadSavedStreams.update_state(state, {})
end
# rubocop:enable Metrics/MethodLength
private
def evaluate_request(sketchup_model, request, state, to_convert)
converter = Converters::Converter.new(sketchup_model)
request.start do |_req, res|
res_data = JSON.parse(res.body)['data']
raise(StandardError) unless res_data
stream_id = res_data['streamCreate']
state = Actions::SaveStream.new(stream_id).update_state(state)
converted = to_convert.map { |entity| converter.convert_to_speckle(entity) }
state = Actions::QueueSend.new(stream_id, converted).update_state(state)
end
state
end
end
end
end
@@ -1,26 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Deactivate diffing.
class DeactivateDiffing < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
puts 'Diffing deactivated!'
speckle_entities = state.speckle_state.speckle_entities
diffing_activated_speckle_entities = speckle_entities.reject do |_id, entity|
entity.active_diffing_stream_id.nil?
end
diffing_activated_speckle_entities.each do |id, entity|
new_entity = entity.deactivate_diffing
speckle_entities = speckle_entities.put(id, new_entity)
end
new_speckle_state = state.speckle_state.with_speckle_entities(speckle_entities)
state.with_speckle_state(new_speckle_state)
end
end
end
end
@@ -1,56 +0,0 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../load_sketchup_model'
require_relative '../collect_preferences'
module SpeckleConnector
module Actions
module Events
# Handle events that are triggered by the {AppObserver}.
class AppEventAction < EventAction
# Handle loading new or existing model
class OnNewOrChangedModel
# Handle events when the new or existing model is loaded in Sketchup
# @param state [States::State] the current state of speckle application
# @param event_data [Array<(Sketchup::Model)>] the event data for the given event. It consists of
# a double array with a single element that is the {Sketchup::Model} object of the loaded model.
def self.update_state(state, event_data)
return state unless event_data&.any?
model = event_data.flatten.first
# LoadSketchupModel action should be responsible to update all model specific data for state and then
# should notify the UI to update it's components.
new_state = Actions::LoadSketchupModel.update_state(state, model)
# Action to let UI to render itself with new preferences state
# TODO: Later UI should be updated if any stream is invalid after
# we collected speckle_entities appropriately
CollectPreferences.update_state(new_state, {})
end
end
# Run actions that are needed before the sketchup quits
class OnQuit
# Handle when Sketchup application closes
# @param state [States::State] the current state of speckle application
# @param _event_data [Array] the event data
# @return [States::State] the transformed state object
def self.update_state(state, _event_data)
state
end
end
# Handlers that are used to handle specific events
ACTIONS = {
onNewModel: OnNewOrChangedModel,
onOpenModel: OnNewOrChangedModel,
onQuit: OnQuit
}.freeze
def self.actions
ACTIONS
end
end
end
end
end
@@ -1,84 +0,0 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../../sketchup_model/utils/face_utils'
require_relative '../../constants/dict_constants'
module SpeckleConnector
module Actions
module Events
# Event actions related to entities.
class EntitiesEventAction < EventAction
# Event action when element added.
class OnElementAdded
# @param state [States::State] the current state of the SpeckleConnector Application
def self.update_state(state, event_data)
modified_entities = event_data.to_a.collect { |e| e[1] }
# do not copy speckle base object specific attributes, because they are entity specific
modified_entities.each { |entity| entity.delete_attribute(SPECKLE_BASE_OBJECT) }
state
end
end
# Event action when element modified.
class OnElementModified
# @param state [States::State] the current state of the SpeckleConnector Application
def self.update_state(state, event_data)
speckle_state = state.speckle_state
modified_entity = event_data[0][1]
if modified_entity.is_a?(Sketchup::Face)
path = state.sketchup_state.sketchup_model.active_path
modified_faces = SketchupModel::Utils::FaceUtils.near_faces(modified_entity.edges)
path_objects = path.nil? ? [] : path + path.collect(&:definition)
parent_ids = path_objects.collect(&:persistent_id)
ids_to_invalidate = modified_faces.collect(&:persistent_id) + parent_ids
entities_to_invalidate = speckle_entities_to_invalidate(speckle_state, ids_to_invalidate)
new_speckle_state = invalidate_speckle_entities(speckle_state, entities_to_invalidate)
# This is the place we can send information to UI for diffing check
diffing = state.user_state.preferences[:user][:diffing]
new_speckle_state = new_speckle_state.with_invalid_streams_queue if diffing
return state.with_speckle_state(new_speckle_state)
end
state
end
# @param speckle_state [States::SpeckleState] the current state of the Speckle
def self.speckle_entities_to_invalidate(speckle_state, ids)
speckle_state.speckle_entities.to_h.select { |id, _| ids.include?(id) }
end
# @param speckle_state [States::SpeckleState] the current state of the Speckle
def self.invalidate_speckle_entities(speckle_state, entities_to_invalidate)
speckle_entities = speckle_state.speckle_entities
entities_to_invalidate.each do |id, speckle_entity|
edited_speckle_entity = speckle_entity.with_invalid
speckle_entities = speckle_entities.put(id, edited_speckle_entity)
end
speckle_state.with_speckle_entities(speckle_entities)
end
end
# Event action when element removed.
class OnElementRemoved
# @param state [States::State] the current state of the SpeckleConnector Application
def self.update_state(state, _event_data)
# TODO: Do state updates when element removed
state
end
end
# Handlers that are used to handle specific events
ACTIONS = {
onElementRemoved: OnElementRemoved,
onElementAdded: OnElementAdded,
onElementModified: OnElementModified
}.freeze
def self.actions
ACTIONS
end
end
end
end
end
@@ -1,34 +0,0 @@
# frozen_string_literal: true
module SpeckleConnector
module Actions
# This module contains actions that are performed to handle events triggered by observers in Sketchup.
module Events
# Base action for Handling events
class EventAction
def self.actions
raise NoMethodError, 'Implement in a subclass'
end
# Handle the events that were collected by the observer. In case of the selection observer,
# we only need to handle the events once if any of the events actually happened.
# @param state [States::State] the current state of the SpeckleConnector Application
# @param events [Hash{Symbol=>Array}] the event data grouped by the event name
# @return [States::State] the transformed state
def self.update_state(state, events)
# Don't do anything if there are no events for this action
return state unless events
actions = self.actions
actions.each do |event_name, action|
next unless events.key?(event_name)
event_data = events[event_name]
state = action.update_state(state, event_data)
end
state
end
end
end
end
end
@@ -1,41 +0,0 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../load_sketchup_model'
module SpeckleConnector
module Actions
module Events
# Handle events that are triggered by the {ModelObserver}.
class ModelEventAction < EventAction
# Handle loading new or existing model
class OnActivePathChanged
# Handle events when the new or existing model is loaded in Sketchup
# @param state [States::State] the current state of speckle application
# @param event_data [Array<(Sketchup::Model)>] the event data for the given event. It consists of
# a double array with a single element that is the {Sketchup::Model} object of the loaded model.
def self.update_state(state, _event_data)
sketchup_state = state.sketchup_state
active_path = sketchup_state.sketchup_model.active_path
observers = state.speckle_state.observers
update_object_observers(active_path, observers)
return state
end
def self.update_object_observers(path, observers)
path[-1].definition.entities.add_observer(observers[ENTITIES_OBSERVER]) unless path.nil?
end
end
# Handlers that are used to handle specific events
ACTIONS = {
onActivePathChanged: OnActivePathChanged
}.freeze
def self.actions
ACTIONS
end
end
end
end
end
@@ -1,30 +0,0 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../mapper_selection_changed'
require_relative '../../mapper/category/revit_category'
require_relative '../../sketchup_model/reader/speckle_entities_reader'
require_relative '../../sketchup_model/reader/mapper_reader'
require_relative '../../sketchup_model/query/entity'
module SpeckleConnector
module Actions
module Events
# Update selected speckle objects when the selection changes for mapper tool.
class SelectionEventAction < EventAction
# @param state [States::State] the current state of Speckle application.
# @return [States::State] the new updated state object
def self.update_state(state, event_data)
return state unless event_data&.any?
# Get sketchup selection
sketchup_selection = state.sketchup_state.sketchup_model.selection
# Collect and return mapper selection info.
# Later we can add more selection info for different scopes.
MapperSelectionChanged.new(sketchup_selection).update_state(state)
end
end
end
end
end
@@ -1,36 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Hide entities that selected from mapped elements table.
class HideMappingsFromTable < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, data)
# Flat entities to clear mappings
flat_entities = SketchupModel::Query::Entity.flat_entities(state.sketchup_state.sketchup_model.entities)
# Collect entity ids to clear mappings
entity_ids = data.collect { |_, entities| entities['selectedElements'].collect { |e| e['entityId'] } }.flatten
# Store speckle state to update with mapped entities.
flat_entities.each do |entity|
next unless entity_ids.include?(entity.persistent_id)
if entity.is_a?(Sketchup::ComponentDefinition)
entity.instances.each do |instance|
instance.hidden = true
end
end
entity.hidden = true
end
Events::SelectionEventAction.update_state(state, { clear: true })
end
end
end
end
@@ -1,20 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../accounts/accounts'
require_relative 'load_saved_streams'
module SpeckleConnector
module Actions
# Action to initialize local accounts from database.
class InitLocalAccounts < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
puts 'Initialisation of Speckle accounts requested by plugin'
accounts_data = state.speckle_state.accounts
state.with_add_queue('loadAccounts', accounts_data.to_json, [])
end
end
end
end
@@ -1,30 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'add_material'
require_relative '../constants/mat_constants'
module SpeckleConnector
module Actions
# Action to initialize materials
class InitializeMaterials < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state)
new_state = recreate_material(state, DEFAULT_NAMES[MAT_ADD], DEFAULT_COLORS[MAT_ADD], MAT_ADD)
new_state = recreate_material(new_state, DEFAULT_NAMES[MAT_EDIT], DEFAULT_COLORS[MAT_EDIT], MAT_EDIT)
recreate_material(new_state, DEFAULT_NAMES[MAT_REMOVE], DEFAULT_COLORS[MAT_REMOVE], MAT_REMOVE)
end
def self.recreate_material(state, name, color, id, alpha: nil)
Actions::AddMaterial.update_state(
state,
material_name: name,
color: color,
material_id: id,
alpha: alpha
)
end
end
end
end
@@ -1,35 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../states/state'
require_relative '../states/speckle_state'
require_relative '../states/sketchup_state'
require_relative '../accounts/accounts'
require_relative '../preferences/preferences'
require_relative '../constants/observer_constants'
module SpeckleConnector
module Actions
# Initialization of the real state of the speckle.
class InitializeSpeckle < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, observers)
attach_app_observer!(observers[APP_OBSERVER])
accounts = SpeckleConnector::Accounts.load_accounts
speckle_state = States::SpeckleState.new(accounts, observers, {}, {})
# This should be the only point that `Sketchup_active_model` passed to application state.
sketchup_state = States::SketchupState.new(Sketchup.active_model)
preferences = Preferences.read_preferences(sketchup_state.sketchup_model)
user_state_with_preferences = state.user_state.with_preferences(preferences)
state = States::State.new(user_state_with_preferences, speckle_state, sketchup_state, false)
# This is where we attach observers to related model objects like selection, entities..
Actions::LoadSketchupModel.update_state(state, sketchup_state.sketchup_model)
end
def self.attach_app_observer!(observer)
Sketchup.add_observer(observer)
end
end
end
end
@@ -1,64 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Isolate entities that selected from mapped elements table.
class IsolateMappingsFromTable < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/CyclomaticComplexity
def self.update_state(state, data)
sketchup_model = state.sketchup_state.sketchup_model
# Hide all entities first
sketchup_model.entities.each do |ent|
ent.hidden = true
end
# Flat entities to isolate mappings
flat_entities = SketchupModel::Query::Entity.flat_entities(sketchup_model.entities)
comp_flat_entities = flat_entities.grep(Sketchup::ComponentInstance) + flat_entities.grep(Sketchup::Group) +
flat_entities.grep(Sketchup::ComponentDefinition)
face_edge_flat_entities = flat_entities.grep(Sketchup::Face) + flat_entities.grep(Sketchup::Edge)
# Collect entity ids to clear mappings
selected_elements = data.collect { |_, entities| entities['selectedElements'] }.flatten
comps_or_groups, faces_or_edges = selected_elements.partition do |e|
e['entityType'] == 'Component' || e['entityType'] == 'Definition' || e['entityType'] == 'Group'
end
faces_or_edges_ids = faces_or_edges.collect { |e| e['entityId'] }
face_edge_flat_entities.select { |e| faces_or_edges_ids.include?(e.persistent_id) }.each do |entity|
entity.hidden = false
end
comps_or_groups_ids = comps_or_groups.collect { |e| e['entityId'] }
comp_flat_entities.select { |e| comps_or_groups_ids.include?(e.persistent_id) }.each do |entity|
if entity.is_a?(Sketchup::ComponentDefinition)
entity.instances.each do |instance|
instance.hidden = false
end
end
entity.hidden = false
end
Events::SelectionEventAction.update_state(state, { clear: true })
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
end
end
end
@@ -1,18 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Action to load saved streams.
class LoadSavedStreams < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
(saved_streams = state.sketchup_state.sketchup_model
.attribute_dictionary('Speckle', true)['saved_streams']) or []
state.with_add_queue('setSavedStreams', saved_streams, [])
end
end
end
end
@@ -1,63 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'initialize_materials'
require_relative '../sketchup_model/reader/speckle_entities_reader'
require_relative '../sketchup_model/reader/mapper_reader'
require_relative '../preferences/preferences'
require_relative '../states/state'
require_relative '../states/sketchup_state'
require_relative '../constants/observer_constants'
module SpeckleConnector
module Actions
# Switch sketchup model wit a new one
class LoadSketchupModel < Action
# Replace current model state with the state of a new model. This action is triggered when user opens new or
# existing Sketchup model.
# @param state [States::State] the current state of Speckle
# @param additional_parameters [Array] parameters that the action takes
# @return [States::State] the new updated state object
def self.update_state(state, sketchup_model)
# Init sketchup state again with new model
new_sketchup_state = States::SketchupState.new(sketchup_model)
sketchup_model.rendering_options['DisplaySectionPlanes'] = true
new_state = state.with(:@sketchup_state => new_sketchup_state)
# Init materials again
new_state = InitializeMaterials.update_state(new_state)
# Read speckle entities
new_speckle_entities = SketchupModel::Reader::SpeckleEntitiesReader.read(sketchup_model.entities)
new_speckle_state = new_state.speckle_state.with_speckle_entities(Immutable::Hash.new(new_speckle_entities))
# Read mapped entities
new_mapped_entities = SketchupModel::Reader::MapperReader.read_mapped_entities(sketchup_model.entities)
new_speckle_state = new_speckle_state.with_mapped_entities(Immutable::Hash.new(new_mapped_entities))
new_state = new_state.with_speckle_state(new_speckle_state)
# Read preferences from database and model.
preferences = Preferences.read_preferences(new_state.sketchup_state.sketchup_model)
new_user_state = new_state.user_state.with_preferences(preferences)
new_state = new_state.with(:@user_state => new_user_state)
attach_observers(sketchup_model, new_state.speckle_state.observers)
new_state
end
# Attach observers to the sketchup model
# @param sketchup_model [Sketchup::Model] the model to attach observers to
# @param observers [Hash{Class=>}] the observer objects indexed by their class that will be attached
def self.attach_observers(sketchup_model, observers)
selection = sketchup_model.selection
selection.add_observer(observers[SELECTION_OBSERVER])
# layers = sketchup_model.layers
# layers.add_observer(observers[LAYERS_OBSERVER_NAME])
entities = sketchup_model.entities
entities.add_observer(observers[ENTITIES_OBSERVER])
sketchup_model.add_observer(observers[MODEL_OBSERVER])
# materials = sketchup_model.materials
# materials.add_observer(observers[MATERIALS_OBSERVER_NAME])
# pages = sketchup_model.pages
# pages.add_observer(observers[PAGES_OBSERVER_NAME])
end
end
end
end
@@ -1,20 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../sketchup_model/reader/mapper_reader'
module SpeckleConnector
module Actions
# Triggers when mapped entities updated.
class MappedEntitiesUpdated < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data = nil)
mapped_entities = SketchupModel::Reader::MapperReader
.mapped_entity_details(state.speckle_state.speckle_mapper_state.mapped_entities.values.to_a)
state.with_mapped_entities_queue(mapped_entities)
end
end
end
end
@@ -1,22 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../mapper/category/revit_category'
require_relative '../mapper/category/revit_family_category'
module SpeckleConnector
module Actions
# Collects mapper selection info.
class MapperInitialized < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
init_parameters = {
categories: Mapper::Category::RevitCategory.to_a,
familyCategories: Mapper::Category::RevitFamilyCategory.to_a
}.freeze
state.with_mapper_init_queue(init_parameters)
end
end
end
end
@@ -1,200 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../mapper/category/revit_category'
require_relative '../mapper/category/revit_family_category'
require_relative '../sketchup_model/reader/mapper_reader'
require_relative '../sketchup_model/reader/speckle_entities_reader'
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
module SpeckleConnector
module Actions
# Collects mapper selection info.
class MapperSelectionChanged < Action
READER = SketchupModel::Reader
DICTIONARY = SketchupModel::Dictionary
def initialize(selection)
super()
@selection = selection
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
# Get mapping info from selection.
mapping = get_mapping_info(state, @selection)
state.with_mapper_selection_queue(mapping)
end
def filter_out_levels(selection)
selection.reject do |e|
DICTIONARY::SpeckleEntityDictionaryHandler
.get_attribute(e, :speckle_type) == OBJECTS_BUILTELEMENTS_REVIT_LEVEL
end
end
def get_mapping_info(state, selection)
source_exist = !state.speckle_state.speckle_mapper_state.mapper_source.nil?
selection = filter_out_levels(selection)
grouped_by_type = group_by_type(selection)
supported_entity_count = grouped_by_type.length
# Return empty method list if there is no supported entity to map.
return EMPTY_SELECTION if supported_entity_count == 0
# Return Direct Shape itself if multiple kinds of element are selected like Edge and Face.
# OR single type is equal to only direct shape supports.
return multiple_supported_selection_info(selection) if supported_entity_count > 1
# FIXME: Distinguish selection info according to selection elegantly!!!
if grouped_by_type.keys.first == Sketchup::ComponentInstance
return component_selection_info(selection, source_exist)
end
return group_selection_info(selection) if grouped_by_type.keys.first == Sketchup::Group
if supported_entity_count > 1 ||
(supported_entity_count == 1 &&
MAPPER_DIRECT_SHAPE_SUPPORTED_ENTITY_TYPES.include?(grouped_by_type.keys.first))
if source_exist
return direct_shape_selection_info_with_source(selection, [])
else
return direct_shape_selection_info(selection, source_exist)
end
end
# Only single type selections remained after this point.
return face_selection_info(state, grouped_by_type.values.first) if grouped_by_type.keys.first == Sketchup::Face
return edge_selection_info(state, grouped_by_type.values.first) if grouped_by_type.keys.first == Sketchup::Edge
EMPTY_SELECTION
end
MAPPER_SUPPORTED_ENTITY_TYPES = [
Sketchup::ComponentInstance,
Sketchup::Group,
Sketchup::Face,
Sketchup::Edge
].freeze
MAPPER_DIRECT_SHAPE_SUPPORTED_ENTITY_TYPES = [
Sketchup::ComponentInstance,
Sketchup::Group
].freeze
EMPTY_SELECTION = {
selection: [],
mappingMethods: []
}.freeze
def multiple_supported_selection_info(selection)
{
selection: SketchupModel::Reader::MapperReader.entities_schema_details(selection),
mappingMethods: ['Direct Shape']
}.freeze
end
def component_selection_info(selection, source_exist)
if source_exist
{
selection: SketchupModel::Reader::MapperReader.entities_schema_details(selection),
mappingMethods: ['Direct Shape', 'New Revit Family', 'Family Instance']
}.freeze
else
{
selection: SketchupModel::Reader::MapperReader.entities_schema_details(selection),
mappingMethods: ['Direct Shape', 'New Revit Family']
}.freeze
end
end
def group_selection_info(selection)
{
selection: SketchupModel::Reader::MapperReader.entities_schema_details(selection),
mappingMethods: ['Direct Shape']
}.freeze
end
def direct_shape_selection_info(selection, source_exist)
methods = ['Direct Shape', 'New Revit Family']
methods.append('Family Instance') if source_exist
{
selection: SketchupModel::Reader::MapperReader.entities_schema_details(selection),
mappingMethods: methods
}.freeze
end
def direct_shape_selection_info_with_default(selection, methods)
{
selection: SketchupModel::Reader::MapperReader.entities_schema_details(selection),
mappingMethods: ['Direct Shape'] + methods
}.freeze
end
def direct_shape_selection_info_with_source(filtered_selection, methods)
instances = @selection.grep(Sketchup::ComponentInstance)
selected_level = instances.find do |i|
DICTIONARY::SpeckleEntityDictionaryHandler
.get_attribute(i, :speckle_type) == OBJECTS_BUILTELEMENTS_REVIT_LEVEL
end
selected_level_name = nil
if selected_level
selected_level_name = DICTIONARY::SpeckleEntityDictionaryHandler.get_attribute(selected_level, :name)
end
{
selection: READER::MapperReader.entities_schema_details(filtered_selection),
mappingMethods: ['Direct Shape'] + methods,
categories: Mapper::Category::RevitCategory.to_a,
selectedLevelName: selected_level_name
}.freeze
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
def face_selection_info(state, faces)
source_exist = !state.speckle_state.speckle_mapper_state.mapper_source.nil?
grouped_by_verticality = faces.group_by { |face| face.normal.perpendicular?(VECTOR_Z) }
return direct_shape_selection_info(faces, source_exist) if grouped_by_verticality.length == 2
if source_exist
if grouped_by_verticality.keys.first
direct_shape_selection_info_with_source(faces, ['Wall'])
else
direct_shape_selection_info_with_source(faces, ['Floor'])
end
else
if grouped_by_verticality.keys.first
direct_shape_selection_info_with_default(faces, ['Default Wall'])
else
direct_shape_selection_info_with_default(faces, ['Default Floor'])
end
end
end
def edge_selection_info(state, edges)
source_exist = !state.speckle_state.speckle_mapper_state.mapper_source.nil?
if source_exist
methods = ['Column', 'Beam', 'Pipe', 'Duct']
direct_shape_selection_info_with_source(edges, methods)
else
default_methods = ['Default Column', 'Default Beam', 'Default Pipe', 'Default Duct']
direct_shape_selection_info_with_default(edges, default_methods)
end
end
def group_by_type_old(selection)
selection.group_by(&:class).filter_map do |group|
[group.first, group] if MAPPER_SUPPORTED_ENTITY_TYPES.include?(group.first)
end.to_h
end
def group_by_type(selection)
selection.select { |s| MAPPER_SUPPORTED_ENTITY_TYPES.include?(s.class) }.group_by(&:class)
end
end
end
end
@@ -1,77 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../constants/type_constants'
require_relative '../mapper/mapper_source'
require_relative '../speckle_objects/built_elements/revit/revit_element_type'
module SpeckleConnector
module Actions
# Action to update mapper source.
class MapperSourceUpdated < Action
def initialize(base, stream_id, commit_id)
super()
@base = base
@stream_id = stream_id
@commit_id = commit_id
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
levels = convert_levels(state, @base['@Levels'])
types = convert_types(@base['@Types'])
family_instances = convert_family_instance_types(@base['@Types'])
mapper_source = Mapper::MapperSource.new(@stream_id, @commit_id, levels, types)
new_speckle_state = state.speckle_state.with_mapper_source(mapper_source)
state = state.with_speckle_state(new_speckle_state)
state.with_add_queue('mapperSourceUpdated', @stream_id, [
{ is_string: false, val: levels.to_json },
{ is_string: false, val: types.to_json },
{ is_string: false, val: family_instances.to_json }
])
end
def convert_types(types)
types.collect do |type, type_elements|
next if type_elements.nil? || !type_elements.is_a?(Array) || type == '__closure'
type = type[1..-1] if type[0] == '@'
elements = type_elements.map do |type_element|
SpeckleObjects::BuiltElements::Revit::RevitElementType.to_native(type_element)
end
elements = elements.group_by { |e| e[:family] }
[type, elements]
end.compact.to_h
end
def convert_family_instance_types(types)
family_instance_types = {}
types.each do |type, type_elements|
next if type_elements.nil? || !type_elements.is_a?(Array) || type == '__closure'
# skip type if there is no any revit symbol element type
symbol_element_types = type_elements.select do |t|
t['speckle_type'] == OBJECTS_BUILTELEMENTS_REVIT_REVITSYMBOLELEMENTTYPE &&
t['placementType'] == 'OneLevelBased'
end
next if symbol_element_types.empty?
elements = type_elements.map do |type_element|
SpeckleObjects::BuiltElements::Revit::RevitElementType.to_native(type_element)
end
elements = elements.group_by { |e| e[:family] }
family_instance_types.merge!(elements)
end
family_instance_types
end
def convert_levels(state, levels)
levels.collect do |level|
SpeckleObjects::BuiltElements::Level.to_native(state, level, @stream_id)
end
end
end
end
end
@@ -1,36 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../ext/sqlite3'
require_relative '../accounts/accounts'
require_relative '../constants/path_constants'
require_relative '../sketchup_model/dictionary/speckle_model_dictionary_handler'
module SpeckleConnector
module Actions
# When preference updated by UI.
class ModelPreferencesUpdated < Action
def initialize(pref, value)
super()
@preference = pref
@value = value
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
model = state.user_state.preferences[:model].dup
model[@preference.to_sym] = @value
new_preferences = state.user_state.preferences.put(:model, model)
SketchupModel::Dictionary::SpeckleModelDictionaryHandler.set_attribute(
state.sketchup_state.sketchup_model,
@preference.to_sym,
@value,
'Speckle'
)
new_user_state = state.user_state.with_preferences(new_preferences)
state.with_user_state(new_user_state)
end
end
end
end
@@ -1,35 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'events/app_event_action'
require_relative 'events/entities_event_action'
require_relative 'events/model_event_action'
require_relative 'events/selection_event_action'
require_relative '../constants/observer_constants'
module SpeckleConnector
module Actions
# Handle events that were collected by observers
class OnEventsAction < Action
RUN_ORDER = {
APP_OBSERVER => Events::AppEventAction,
ENTITIES_OBSERVER => Events::EntitiesEventAction,
MODEL_OBSERVER => Events::ModelEventAction,
# MATERIALS_OBSERVER => Events::MaterialsEventAction,
# LAYERS_OBSERVER => Events::LayerEventAction,
# PAGES_OBSERVER => Events::PagesEventAction,
SELECTION_OBSERVER => Events::SelectionEventAction
}.freeze
def self.update_state(state, events)
RUN_ORDER.each do |observer_name, action|
next unless events.key?(observer_name)
parameters = events[observer_name]
state = action.update_state(state, parameters)
end
state
end
end
end
end
@@ -1,45 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../accounts/accounts'
require_relative '../actions/create_stream'
require_relative '../actions/queue_send'
require_relative '../convertors/to_speckle'
module SpeckleConnector
module Actions
# Sends to speckle.
class OneClickSend < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state)
puts 'send to speckle'
default_account = Accounts.default_account
if default_account.nil?
puts 'No local account found. Please refer to speckle.guide for more information.'
return state
end
sketchup_model = state.sketchup_state.sketchup_model
to_convert = sketchup_model.selection.count > 0 ? sketchup_model.selection : sketchup_model.entities
first_saved_stream = first_saved_stream(sketchup_model)
action = if first_saved_stream.nil?
Actions::CreateStream.new
else
Actions::QueueSend.new(first_saved_stream, convert_to_speckle(sketchup_model, to_convert))
end
action.update_state(state)
end
def self.first_saved_stream(model)
(saved_streams = model.attribute_dictionary('speckle', true)['streams']) or []
saved_streams.nil? || saved_streams.empty? ? nil : saved_streams[0]
end
def self.convert_to_speckle(sketchup_model, to_convert)
converter = Converters::ToSpeckle.new(sketchup_model)
to_convert.map { |entity| converter.convert(entity) }
end
end
end
end
@@ -1,32 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../states/state'
require_relative '../states/speckle_state'
require_relative '../actions/send_from_queue'
module SpeckleConnector
module Actions
# Send queue from state.
class QueueSend < Action
def initialize(stream_id, converted)
super()
@stream_id = stream_id
@converted = converted
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
to_send = { stream_id: @stream_id, converted: @converted }
new_speckle_state = state.speckle_state.with(:@stream_queue => to_send)
new_state = state.with(:@speckle_state => new_speckle_state)
if new_state.is_connected
action = Actions::SendFromQueue.new(@stream_id)
new_state = action.update_state(new_state)
end
new_state
end
end
end
end
@@ -1,43 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../convertors/units'
require_relative '../convertors/to_native'
require_relative '../convertors/clean_up'
module SpeckleConnector
module Actions
# Action to receive objects from Speckle Server.
class ReceiveObjects < Action
# rubocop:disable Metrics/ParameterLists
def initialize(stream_id, base, stream_name, branch_name, branch_id, source_app)
super()
@stream_id = stream_id
@base = base
@stream_name = stream_name
@branch_name = branch_name
@branch_id = branch_id
@source_app = source_app
end
# rubocop:enable Metrics/ParameterLists
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
converter = Converters::ToNative.new(state, @stream_id, @stream_name, @branch_name, @source_app)
# Have side effects on the sketchup model. It effects directly on the entities by adding new objects.
start_time = Time.now.to_f
state.sketchup_state.sketchup_model.start_operation('Receive Speckle Objects', true)
state = converter.receive_commit_object(@base)
if state.user_state.model_preferences[:merge_coplanar_faces]
Converters::CleanUp.merge_coplanar_faces(converter.converted_faces)
end
state.sketchup_state.sketchup_model.commit_operation
elapsed_time = (Time.now.to_f - start_time).round(3)
puts "==== Converting to Native executed in #{elapsed_time} sec ===="
puts "==== Source application is #{@source_app}. ===="
state.with_add_queue('finishedReceiveInSketchup', @stream_id, [])
end
end
end
end
@@ -1,22 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../accounts/accounts'
require_relative 'load_saved_streams'
module SpeckleConnector
module Actions
# Action to reload accounts from database.
class ReloadAccounts < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
puts 'Reload of Speckle accounts requested by plugin'
new_speckle_state = state.speckle_state.with_accounts(Accounts.load_accounts)
state = state.with_speckle_state(new_speckle_state)
accounts_data = state.speckle_state.accounts
state.with_add_queue('loadAccounts', accounts_data.to_json, [])
end
end
end
end
@@ -1,29 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../accounts/accounts'
require_relative '../convertors/units'
require_relative '../convertors/converter'
module SpeckleConnector
module Actions
# Action to remove stream.
# Currently it is not a state changer.
class RemoveStream < Action
def initialize(stream_id)
super()
@stream_id = stream_id
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
speckle_dict = state.sketchup_state.sketchup_model.attribute_dictionary('Speckle', true)
saved = speckle_dict['saved_streams'] || []
saved -= [@stream_id]
speckle_dict['saved_streams'] = saved
state
end
end
end
end
@@ -1,27 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../accounts/accounts'
module SpeckleConnector
module Actions
# Save stream.
# Currently it is not a state changer.
class SaveStream < Action
def initialize(stream_id)
super()
@stream_id = stream_id
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
speckle_dict = state.sketchup_state.sketchup_model.attribute_dictionary('Speckle', true)
saved = speckle_dict['saved_streams'] || []
saved = saved.empty? ? [@stream_id] : saved.unshift(@stream_id)
speckle_dict['saved_streams'] = saved
state
end
end
end
end
@@ -1,37 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Select entities that selected from mapped elements table.
class SelectMappingsFromTable < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, data)
# Clear first selection
state.sketchup_state.sketchup_model.selection.clear
# Flat entities to clear mappings
flat_entities = SketchupModel::Query::Entity.flat_entities(state.sketchup_state.sketchup_model.entities)
# Collect entity ids to clear mappings
entity_ids = data.collect { |_, entities| entities['selectedElements'].collect { |e| e['entityId'] } }.flatten
# Store speckle state to update with mapped entities.
flat_entities.each do |entity|
next unless entity_ids.include?(entity.persistent_id)
if entity.is_a?(Sketchup::ComponentDefinition)
state.sketchup_state.sketchup_model.selection.add(entity.instances)
end
state.sketchup_state.sketchup_model.selection.add(entity)
end
Events::SelectionEventAction.update_state(state, { clear: true })
end
end
end
end
@@ -1,27 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../accounts/accounts'
module SpeckleConnector
module Actions
# Send already converted objects from queue if exist on stream.
class SendFromQueue < Action
def initialize(stream_id)
super()
@stream_id = stream_id
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
to_send_stream_id = state.speckle_state.stream_queue[:stream_id]
return state if to_send_stream_id == @stream_id || to_send_stream_id.nil?
to_send_converted = state.speckle_state.stream_queue[:converted].to_json
new_state = state.with_add_queue('convertedFromSketchup', to_send_stream_id, [to_send_converted])
new_state.with_empty_stream_queue
end
end
end
end
@@ -1,40 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'deactivate_diffing'
require_relative '../convertors/units'
require_relative '../convertors/to_speckle'
module SpeckleConnector
module Actions
# Send selection to server.
class SendSelection < Action
def initialize(stream_id)
super()
@stream_id = stream_id
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
state = DeactivateDiffing.update_state(state, {})
converter = Converters::ToSpeckle.new(state, @stream_id)
new_speckle_state, base = converter.convert_selection_to_base(state.user_state.preferences)
id, total_children_count, batches, new_speckle_state = converter.serialize(base, new_speckle_state,
state.user_state.preferences)
puts("converted #{base.count} objects for stream #{@stream_id}")
# This is the place we can send information to UI for diffing check
diffing = state.user_state.preferences[:user][:diffing]
new_speckle_state = new_speckle_state.with_invalid_streams_queue if diffing
new_state = state.with_speckle_state(new_speckle_state)
new_state.with_add_queue('convertedFromSketchup', @stream_id, [
{ is_string: false, val: batches },
{ is_string: true, val: id },
{ is_string: false, val: total_children_count }
])
end
end
end
end
@@ -1,20 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Show all entities on the model.
class ShowAllEntities < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
# Show all entities first
state.sketchup_state.sketchup_model.entities.each do |ent|
ent.hidden = false
end
state
end
end
end
end
@@ -1,66 +0,0 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../ext/sqlite3'
require_relative '../accounts/accounts'
require_relative '../constants/path_constants'
module SpeckleConnector
module Actions
# When preference updated by UI.
class UserPreferencesUpdated < Action
def initialize(pref_hash, pref, value)
super()
@preference_hash = pref_hash
@preference = pref
@value = value
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
def update_state(state)
# Init sqlite database
db = Sqlite3::Database.new(SPECKLE_CONFIG_DB_PATH)
# Select data
data = db.exec("SELECT content FROM 'objects' WHERE hash = '#{@preference_hash}'").first.first
# Parse string to hash
data_hash = JSON.parse(data).to_h
# Get current preference value
old_preference_value = data_hash[@preference]
# Return old state if it is equal to new one
return state if @value == old_preference_value
data_hash[@preference] = @value
# Update entry unless equal old to new
db.exec("UPDATE 'objects' SET content = '#{data_hash.to_json}' WHERE hash = '#{@preference_hash}'")
# Close db when process done
db.close
user = state.user_state.preferences[:user].dup
user[@preference.to_sym] = @value
new_preferences = state.user_state.preferences.put(:user, user)
new_user_state = state.user_state.with_preferences(new_preferences)
# This is the place we can send information to UI for diffing check. It is a technical depth!
if @preference == 'diffing'
new_speckle_state = if @value
state.speckle_state.with_invalid_streams_queue
else
state.speckle_state.with_empty_invalid_streams_queue
end
state = state.with_speckle_state(new_speckle_state)
end
state.with_user_state(new_user_state)
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
end
end
end
@@ -1,57 +0,0 @@
# frozen_string_literal: true
require_relative '../actions/clear_queue'
module SpeckleConnector
module App
# Application for the Speckle Connector.
class SpeckleConnectorApp
# @return [Commands::MenuCommandHandler] the commands registered in the extension menu in Sketchup
attr_reader :menu_commands
# @return [States::State] the current states of the app
attr_reader :state
# @return [Ui::UiController] controller for ui views
attr_reader :ui_controller
# @return [Observers::Handler] the observers indexed by their classes to handle
attr_reader :observer_handler
def initialize(menu_commands, state, ui_controller)
@menu_commands = menu_commands
@state = state
@ui_controller = ui_controller
end
def speckle_loaded?
state.speckle_state?
end
def update_ui!
ui_controller.update_ui(state)
end
# Attach observers to application when speckle initialized via menu commands.
def add_observer_handler!(observer_handler)
@observer_handler = observer_handler
end
# Send messages to HtmlDialog if any.
def send_messages!
queue = @state.speckle_state.message_queue
queue.each_value { |value| ui_controller.user_interfaces[Ui::SPECKLE_UI_ID].dialog.execute_script(value) }
update_state!(Actions::ClearQueue)
end
# This is the only function application state will be switched by calling upcoming action with it's parameters
# if any.
def update_state!(action, *parameters)
old_state = @state
@state = action.update_state(old_state, *parameters)
send_messages! if @state.speckle_state.message_queue.any?
update_ui! unless @state.equal?(old_state)
end
end
end
end
@@ -1,39 +0,0 @@
# frozen_string_literal: true
module SpeckleConnector
module Callbacks
# Helper class to serialize messages to send dialog.
class CallbackMessage
# @param callback_name [String] name of the callback command
# @param stream_id [String] id of the stream
# @param parameters [Array<String>] parameters of the callback method call
def self.serialize(callback_name, stream_id, parameters)
if parameters.any?
serialize_with_parameters(callback_name, stream_id, parameters)
else
serialize_without_parameters(callback_name, stream_id)
end
end
# @param callback_name [String] name of the callback command
# @param stream_id [String] id of the stream
# @param parameters [Array<Object>] parameters of the callback method call
def self.serialize_with_parameters(callback_name, stream_id, parameters)
message = "#{callback_name}('#{stream_id}'"
parameters.each { |par| message += par[:is_string] ? ",'#{par[:val]}'" : ",#{par[:val]}" }
message += ')'
message
end
# @param callback_name [String] name of the callback command
# @param stream_id [String] id of the stream
def self.serialize_without_parameters(callback_name, stream_id)
if %w[setSavedStreams loadAccounts].include?(callback_name)
"#{callback_name}(#{stream_id})"
else
"#{callback_name}('#{stream_id}')"
end
end
end
end
end
@@ -1,24 +0,0 @@
# frozen_string_literal: true
require_relative 'command'
module SpeckleConnector
module Commands
# Command to update state of the application.
class ActionCommand < Command
# @param app [App::SpeckleConnectorApp] the app object to run command on
# @param action [#update_state] the action that knows how to change the state of the speckle app
def initialize(app, action)
super(app)
@app = app
@action = action
end
private
def _run(*parameters)
app.update_state!(@action, *parameters)
end
end
end
end
@@ -1,17 +0,0 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/activate_diffing'
module SpeckleConnector
module Commands
# Command to activate diffing for stream.
class ActivateDiffing < Command
def _run(data)
stream_id = data['stream_id']
action = Actions::ActivateDiffing.new(stream_id)
app.update_state!(action)
end
end
end
end
@@ -1,25 +0,0 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/apply_mappings'
module SpeckleConnector
module Commands
# Command to apply mapping for selected entities.
class ApplyMappings < Command
def _run(data)
entities_to_map = data['entitiesToMap']
method = data['method']
category = data['category']
family = data['family']
family_type = data['familyType']
level = data['level']
name = data['name']
is_definition = data['isDefinition']
action = Actions::ApplyMappings.new(entities_to_map, method, category, family,
family_type, level, name, is_definition)
app.update_state!(action)
end
end
end
end
@@ -1,18 +0,0 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/clear_mappings'
module SpeckleConnector
module Commands
# Command to clear mapping for selected entities.
class ClearMappings < Command
def _run(data)
entities_to_map = data['entitiesToClearMap']
is_definition = data['isDefinition']
action = Actions::ClearMappings.new(entities_to_map, is_definition)
app.update_state!(action)
end
end
end
end
-42
View File
@@ -1,42 +0,0 @@
# frozen_string_literal: true
module SpeckleConnector
module Commands
# Base command schema to wrap common operations for all commands.
class Command
# @return [App::SpeckleConnectorApp] the main app object
attr_reader :app
# @return [Ui::View] view object holds dialog and it's state
attr_reader :view
# @@param app [App::SpeckleConnectorApp] the main app object
def initialize(app)
@app = app
@view = app.ui_controller.user_interfaces[Ui::SPECKLE_UI_ID]
end
def run(*parameters)
# Run here common operations that same for each command.
with_observers_disabled do
_run(*parameters)
end
end
private
def with_observers_disabled(&block)
observer_handler = @app.observer_handler
if observer_handler
observer_handler.with_observers_disabled(&block)
else
block.call
end
end
def _run(*_parameters)
raise NotImplementedError, 'Implement in subclass'
end
end
end
end
@@ -1,15 +0,0 @@
# frozen_string_literal: true
require_relative 'command'
module SpeckleConnector
module Commands
# Run this command when the UI is ready to get data
class DialogReady < Command
# Update the selected user interface
def _run(_data)
view.update_view(app.state)
end
end
end
end
@@ -1,51 +0,0 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../states/initial_state'
require_relative '../ui/vue_view'
require_relative '../actions/initialize_speckle'
require_relative '../observers/factory'
module SpeckleConnector
module Commands
# Command to initialize Speckle UI and register it to ui_controller.
# This is the command where we show UI to user.
class InitializeSpeckle < Command
def dialog_title
"Speckle #{CONNECTOR_VERSION}"
end
private
def _run
app = self.app
unless app.state.instance_of?(States::InitialState)
vue_view = app.ui_controller.user_interfaces[Ui::SPECKLE_UI_ID]
vue_view.show
return
end
initialize_speckle(app)
end
# Do the actual Speckle initialization.
def initialize_speckle(app)
# TODO: Initialize here speckle states and observers.
observer_handler = Observers::Factory.create_handler(app)
app.add_observer_handler!(observer_handler)
observers = Observers::Factory.create_observers(observer_handler)
app.update_state!(Actions::InitializeSpeckle, observers)
dialog_specs = {
dialog_id: Ui::SPECKLE_UI_ID,
htm_file: Ui::VUE_UI_HTML,
dialog_title: dialog_title,
height: 950,
width: 300
}
vue_view = Ui::VueView.new(dialog_specs, app)
app.ui_controller.register_ui(Ui::SPECKLE_UI_ID, vue_view)
vue_view.show
end
end
end
end
@@ -1,19 +0,0 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/mapper_source_updated'
module SpeckleConnector
module Commands
# Command to update mapper source.
class MapperSourceUpdated < Command
def _run(data)
base = data['base']
stream_id = data['stream_id']
commit_id = data['commit_id']
action = Actions::MapperSourceUpdated.new(base, stream_id, commit_id)
app.update_state!(action)
end
end
end
end
@@ -1,51 +0,0 @@
# frozen_string_literal: true
module SpeckleConnector
module Commands
# Helper class to register, handle menu and toolbar commands.
class MenuCommandHandler
# @param command [#run] command that can be run
# @param menu_text [String] name of the command that will be displayed on the menu
# @return [UI::Command] the command that can be added to Sketchup menu or toolbar
def self.sketchup_command(command, menu_text)
UI::Command.new(menu_text) do
command.run
end
end
# Validate if the user has started the Speckle and return a status code that can be used by
# {UI::Command#set_validation_proc} to disable menu entries and toolbar entries before Speckle is loaded.
def self.speckle_started(app)
return MF_ENABLED if app.speckle_loaded?
MF_GRAYED
end
def initialize
@menu_commands = {}
@added_to_menu = []
@added_to_toolbar = []
end
def []=(command_id, command)
@menu_commands[command_id] = command
end
# Add command to menu.
def add_to_menu!(command_id, menu)
return if @added_to_menu.include? command_id
menu.add_item(@menu_commands[command_id])
@added_to_menu << command_id
end
# Add command to toolbar.
def add_to_toolbar!(command_id, toolbar)
return if @added_to_toolbar.include? command_id
toolbar.add_item(@menu_commands[command_id])
@added_to_toolbar << command_id
end
end
end
end
@@ -1,18 +0,0 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../accounts/accounts'
require_relative '../actions/model_preference_updated'
module SpeckleConnector
module Commands
# Command to update theme.
class ModelPreferencesUpdated < Command
def _run(data)
preference = data['preference']
new_value = data['value']
app.update_state!(Actions::ModelPreferencesUpdated.new(preference, new_value))
end
end
end
end
@@ -1,18 +0,0 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/connected'
require_relative '../actions/send_from_queue'
module SpeckleConnector
module Commands
# Command to notify connected.
class NotifyConnected < Command
def _run(data)
stream_id = data['stream_id']
app.update_state!(Actions::Connected)
app.update_state!(Actions::SendFromQueue.new(stream_id))
end
end
end
end
@@ -1,22 +0,0 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/receive_objects'
module SpeckleConnector
module Commands
# Command to receive objects from Speckle Server.
class ReceiveObjects < Command
def _run(data)
stream_id = data['stream_id']
base = data['base']
branch_name = data['branch_name']
branch_id = data['branch_id']
stream_name = data['stream_name']
source_app = data['source_app']
action = Actions::ReceiveObjects.new(stream_id, base, stream_name, branch_name, branch_id, source_app)
app.update_state!(action)
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More