Compare commits

..

9 Commits

Author SHA1 Message Date
Oğuzhan Koral e3dceec63c Fix (Plane): Remove orthogonality checks 2023-05-01 19:09:27 +03:00
oguzhankoral e9d34d87cc Remove orthogonality checks 2023-05-01 19:06:16 +03:00
Oğuzhan Koral 5187fded02 Fix (Mesh): Non-planar quad mesh support 2023-05-01 18:08:11 +03:00
oguzhankoral 370825838a Add plane_utils to calculate planarity of 4 points for quad meshes 2023-05-01 17:56:28 +03:00
oguzhankoral bc8eece488 Split all quad meshes 2023-04-28 12:23:16 +01:00
Oğuzhan Koral 4222b1721d Fix (Views): Bug on views recieve 2023-04-12 13:39:01 +03:00
oguzhankoral b475bc96af Disable rubocop for to_native 2023-04-12 13:36:49 +03:00
oguzhankoral f9ac7319ae Fix bug on views recieve 2023-04-12 13:34:09 +03:00
Oğuzhan Koral 2ce3e9150f Release 2.13 2023-03-24 18:08:26 +03:00
371 changed files with 2337 additions and 13496 deletions
Vendored
BIN
View File
Binary file not shown.
+157 -12
View File
@@ -1,17 +1,162 @@
version: 2.1
# Define the jobs we want to run for this project
jobs:
build:
docker:
- image: cimg/base:2023.03
steps:
- run: echo "so long and thanks for all the fish"
orbs:
# Using windows for builds
win: circleci/windows@2.4.0
# Upload artifacts to s3
aws-s3: circleci/aws-s3@2.0.0
jobs:
build-ui:
docker:
- image: "circleci/node:16"
steps:
- checkout
- run:
command: "npm install"
working_directory: "ui"
- run:
command: "npm run build"
working_directory: "ui"
- persist_to_workspace:
root: ./
paths:
- speckle_connector/vue_ui
build-connector: # Reusable job for basic connectors
executor:
name: win/default # comes with python 3.7.3
shell: cmd.exe
parameters:
slug:
type: string
default: ""
steps:
- checkout
- attach_workspace:
at: ./
- run:
name: Create Innosetup signing cert
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 }
$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
- 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
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools
deploy-manager2:
docker:
- image: mcr.microsoft.com/dotnet/sdk:6.0
parameters:
slug:
type: string
os:
type: string
extension:
type: string
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 >>
# Orchestrate our job run sequence
workflows:
build_and_test:
when:
false
build-and-deploy:
jobs:
- build
- get-ci-tools:
filters:
tags:
only: /.*/
- build-ui:
filters:
tags:
only: /.*/
- build-connector:
slug: sketchup
requires:
- get-ci-tools
- build-ui
filters:
tags:
only: /.*/
context: innosetup
- deploy-manager2:
context: do-spaces-speckle-releases
slug: sketchup
os: Win
extension: exe
requires:
- get-ci-tools
- build-ui
- build-connector
filters:
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
-64
View File
@@ -1,64 +0,0 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
name: Build
on:
pull_request:
workflow_call:
outputs:
semver:
description: "The full SemVer 2.0 version of this build, e.g. '3.0.0-alpha.1234' (note: no 'v'-prefix)"
value: ${{ jobs.build.outputs.semver }}
file_version:
description: "The file info version, e.g. '3.0.0.1234'"
value: ${{ jobs.build.outputs.file_version }}
jobs:
build:
runs-on: ubuntu-latest
outputs:
semver: ${{ steps.set-version.outputs.semver }}
file_version: ${{ steps.set-version.outputs.file-version }}
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.10
uses: actions/setup-python@v3
with:
python-version: "3.10"
- id: set-version
name: Set version to output
shell: bash
run: |
TAG=${{ github.ref_name }}
if [[ "${{ github.ref }}" != refs/tags/* ]]; then
TAG="v3.0.99.${{ github.run_number }}"
fi
SEMVER="${TAG#v}"
FILE_VERSION=$(echo "$TAG" | sed -E 's/^v([0-9]+\.[0-9]+\.[0-9]+).*/\1/')
FILE_VERSION="$FILE_VERSION.${{ github.run_number }}"
echo "semver=$SEMVER" >> "$GITHUB_OUTPUT"
echo "file-version=$FILE_VERSION" >> "$GITHUB_OUTPUT"
echo $SEMVER
echo $FILE_VERSION
- name: Set connector version
run: |
python patch_version.py ${{steps.set-version.outputs.semver}}
- uses: montudor/action-zip@v1
with:
args: zip -q -r sketchup.zip vendor speckle_connector_3/ speckle_connector_3.rb
- name: ⬆️ Upload artifacts
uses: actions/upload-artifact@v4
with:
name: output-${{steps.set-version.outputs.semver}}
path: sketchup.zip
retention-days: 1
if-no-files-found: error
compression-level: 0 # no compression
+78
View File
@@ -0,0 +1,78 @@
name: Update issue Status
on:
issues:
types: [closed]
jobs:
update_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ORGANIZATION: specklesystems
PROJECT_NUMBER: 9
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectNext(number: $number) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
echo "$PROJECT_ID"
echo "$STATUS_FIELD_ID"
echo 'DONE_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .settings | fromjson | .options[] | select(.name== "Done") | .id' project_data.json) >> $GITHUB_ENV
echo "$DONE_ID"
- name: Add Issue to project #it's already in the project, but we do this to get its node id!
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $id:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
- name: Update Status
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $status:ID!, $id:ID!, $value:String!) {
set_status: updateProjectNextItemField(
input: {
projectId: $project
itemId: $id
fieldId: $status
value: $value
}
) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f status=$STATUS_FIELD_ID -f id=$ITEM_ID -f value=${{ env.DONE_ID }}
-42
View File
@@ -1,42 +0,0 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
name: Build and deploy
on:
push:
branches: ["main", "installer-test/**"]
tags: ["v3.*.*"] # Manual delivery on every 3.x tag
jobs:
build:
uses: ./.github/workflows/build.yml
deploy-installers:
runs-on: ubuntu-latest
needs: build
env:
IS_PUBLIC_RELEASE: ${{ github.ref_type == 'tag' }}
steps:
- name: 🔫 Trigger Build Installer(s)
uses: the-actions-org/workflow-dispatch@v4.0.0
with:
workflow: Build Installers
repo: specklesystems/connector-installers
token: ${{ secrets.CONNECTORS_GH_TOKEN }}
inputs: '{
"run_id": "${{ github.run_id }}",
"semver": "${{ needs.build.outputs.semver }}",
"file_version": "${{ needs.build.outputs.file_version }}",
"repo": "${{ github.repository }}",
"is_public_release": ${{ env.IS_PUBLIC_RELEASE }}
}'
ref: main
wait-for-completion: true
wait-for-completion-interval: 10s
wait-for-completion-timeout: 10m
display-workflow-run-url: true
display-workflow-run-url-interval: 10s
- uses: geekyeggo/delete-artifact@v5
with:
name: output-*
+50
View File
@@ -0,0 +1,50 @@
name: Move new issues into Project
on:
issues:
types: [opened]
jobs:
track_issue:
runs-on: ubuntu-latest
steps:
- name: Get project data
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ORGANIZATION: specklesystems
PROJECT_NUMBER: 9
run: |
gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
query($org: String!, $number: Int!) {
organization(login: $org){
projectNext(number: $number) {
id
fields(first:20) {
nodes {
id
name
settings
}
}
}
}
}' -f org=$ORGANIZATION -F number=$PROJECT_NUMBER > project_data.json
echo 'PROJECT_ID='$(jq '.data.organization.projectNext.id' project_data.json) >> $GITHUB_ENV
echo 'STATUS_FIELD_ID='$(jq '.data.organization.projectNext.fields.nodes[] | select(.name== "Status") | .id' project_data.json) >> $GITHUB_ENV
- name: Add Issue to project
env:
GITHUB_TOKEN: ${{secrets.GHPROJECT_TOKEN}}
ISSUE_ID: ${{ github.event.issue.node_id }}
run: |
item_id="$( gh api graphql --header 'GraphQL-Features: projects_next_graphql' -f query='
mutation($project:ID!, $id:ID!) {
addProjectNextItem(input: {projectId: $project, contentId: $id}) {
projectNextItem {
id
}
}
}' -f project=$PROJECT_ID -f id=$ISSUE_ID --jq '.data.addProjectNextItem.projectNextItem.id')"
echo 'ITEM_ID='$item_id >> $GITHUB_ENV
+38
View File
@@ -0,0 +1,38 @@
# 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
+2 -2
View File
@@ -10,8 +10,8 @@
settings.json
# vue app build dist folder
speckle_connector_3/vue_ui
speckle_connector_3/html
speckle_connector/vue_ui
speckle_connector/html
# speckle-sharp-ci-tools
/speckle-sharp-ci-tools
+1 -1
View File
@@ -18,7 +18,7 @@ AllCops:
- '_tools/su_attributes/**/*.rb'
- '_sqlite3/**/*.rb'
- 'ui/**/*'
- 'speckle_connector_3/src/ext/**/*.rb'
- 'speckle_connector/src/ext/**/*.rb'
- 'vendor/bundle/**/*'
- 'tests/**/*.rb'
SketchUp:
+1 -1
View File
@@ -1,6 +1,6 @@
require_paths:
- "C:/Program Files/SketchUp/SketchUp 2021/Tools"
- speckle_connector_3
- speckle_connector
require:
- sketchup-api-stubs
+1 -3
View File
@@ -24,8 +24,6 @@ group :development do
gem 'rubycritic', '~> 4.3', '>= 4.3.3', require: false
# Auto completions for SketchUp API.
gem 'sketchup-api-stubs'
# Runtime dependency of skippy for Ruby 3.2. Have it!
gem 'sorted_set', '~> 1.0'
# Aid with common SketchUp extension tasks.
gem 'skippy', '~> 0.5.2.a'
gem 'skippy', '~> 0.4.1.a'
end
+8 -15
View File
@@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.4)
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
axiom-types (0.1.1)
@@ -26,7 +26,7 @@ GEM
path_expander (~> 1.0)
ruby_parser (~> 3.1, > 3.1.0)
sexp_processor (~> 4.8)
git (1.19.1)
git (1.12.0)
addressable (~> 2.8)
rchardet (~> 1.8)
ice_nine (0.11.2)
@@ -48,10 +48,10 @@ GEM
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
public_suffix (5.0.1)
psych (3.3.4)
public_suffix (5.0.0)
rainbow (3.1.1)
rake (13.0.6)
rbtree (0.4.6)
rchardet (1.8.0)
reek (6.1.1)
kwalify (~> 0.7.0)
@@ -90,7 +90,6 @@ GEM
simplecov (>= 0.17.0)
tty-which (~> 0.4.0)
virtus (~> 1.0)
set (1.1.0)
sexp_processor (4.16.1)
simplecov (0.21.2)
docile (~> 1.1)
@@ -99,15 +98,11 @@ GEM
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
sketchup-api-stubs (0.7.8)
skippy (0.5.2.a)
skippy (0.4.3.a)
git (~> 1.3)
naturally (~> 2.1)
sorted_set (~> 1.0)
thor (>= 0.19, < 2.0)
sorted_set (1.0.3)
rbtree
set (~> 1.0)
thor (1.3.1)
thor (~> 0.19)
thor (0.20.3)
thread_safe (0.3.6)
tty-which (0.4.2)
unicode-display_width (1.8.0)
@@ -118,7 +113,6 @@ GEM
equalizer (~> 0.0, >= 0.0.9)
PLATFORMS
x64-mingw-ucrt
x64-mingw32
x64-unknown
x86_64-linux
@@ -134,8 +128,7 @@ DEPENDENCIES
rubocop-sketchup
rubycritic (~> 4.3, >= 4.3.3)
sketchup-api-stubs
skippy (~> 0.5.2.a)
sorted_set (~> 1.0)
skippy (~> 0.4.1.a)
BUNDLED WITH
2.3.25
-11
View File
@@ -1,11 +0,0 @@
workflow: GitFlow/v1
next-version: 3.0.0
mode: ManualDeployment
branches:
main:
label: rc
develop:
regex: ^dui3/alpha$
label: beta
unknown:
increment: None
+4 -18
View File
@@ -49,9 +49,9 @@ 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_3.rb`
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_3`.
will start to read extension. Source folder is `speckle_connector`.
### 2. **User Interface**
@@ -64,7 +64,7 @@ This repo is split into three parts:
we use extensions as native part of the source `ruby` code.
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_3/src/ext`. Building this project should be only
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
@@ -115,23 +115,9 @@ You can now open up the repo in VS Code or you can use JetBrains' tools RubyMine
If you will use VS Code, make sure you've installed the Ruby extension for VS Code.
#### RubyMine
To debug:
- Add configuration as **'Ruby remote debug'**
- Remote host: localhost
- Remote port: 7000
- Remote root folder: <repo_path>
- Local port: 26162
- Local root folder: <repo_path>
- Run below script
bundle exec skippy sketchup:debug 2024
- When sketchup opened, click Debug button on RubyMine
### Loading the Speckle Connector Plugin
1. Find already prepared `speckle_connector_3_loader.rb` file on the `_tools`
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 -3
View File
@@ -32,12 +32,12 @@ end
# Glob pattern to match source files. Defaults to FileList['.'].
ruby_critic_paths = FileList[
'speckle_connector_3/**/*.rb',
'speckle_connector_3.rb',
'speckle_connector/**/*.rb',
'speckle_connector.rb',
'tests/**/*.rb'] -
FileList[
'_tools/**/*.rb',
'speckle_connector_3/src/ext/**/*.rb',
'speckle_connector/src/ext/**/*.rb',
]
# for local
-15
View File
@@ -1,15 +0,0 @@
# This is for automated pre-debugger configuration.
# We run skippy first, then activate debugger.
# The purpose of this file to wait till skp is live
# To establish a configuration
# 1. Create 'Run External Tool' before lunch step
# 2. Program -> C:\Ruby32-x64\bin\ruby.exe or whatever
# 3. Arguments -> C:\Users\KORAL\Documents\Git\Speckle\speckle-sketchup\_tools\debugger\bundle_exec_2024.rb or whatever
# 4. Working directory -> C:\Users\KORAL\Documents\Git\Speckle\speckle-sketchup or whatever
# Add a delay of 10 seconds, it is arbitrary, do not hesitate to change for what works best for you
sleep(10)
# Execute the original command
exec('bundle exec skippy sketchup:debug 2024')
+1 -1
View File
@@ -24,7 +24,7 @@ module JF_RubyToolbar
def self.load_toolbar
@last_dir = "#{$LOAD_PATH[0]}/"
@last_dir = @last_dir.gsub('/', '\\\\\\\\')
@last_dir = File.join($JF_RUBYTOOLBAR, 'speckle_connector_3')
@last_dir = File.join($JF_RUBYTOOLBAR, 'speckle_connector')
curdir = File.dirname __FILE__
# create toolbar
@@ -10,7 +10,7 @@
# 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_3_loader.rb' -Target ~\Git\Speckle\speckle-sketchup\_tools\speckle_connector_3_loader.rb
# 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
@@ -32,7 +32,7 @@ $LOAD_PATH << File.join(speckle_path, '_tools')
$JF_RUBYTOOLBAR = speckle_path
# rubocop:enable Style/GlobalVars
files = %w[speckle_connector_3 jf_RubyPanel su_attributes]
files = %w[speckle_connector jf_RubyPanel su_attributes]
files.each do |ruby_file|
puts "Loading #{ruby_file}"
+2 -8
View File
@@ -4,7 +4,7 @@ import sys
def patch_connector(tag):
"""Patches the connector version within the connector file"""
rb_file = "speckle_connector_3.rb"
rb_file = "speckle_connector.rb"
with open(rb_file, "r") as file:
lines = file.readlines()
@@ -15,12 +15,6 @@ def patch_connector(tag):
print(f"Patched connector version number in {rb_file}")
break
for (index, line) in enumerate(lines):
if 'DEV_MODE = ' in line:
lines[index] = f' DEV_MODE = false\n'
print(f"Patched dev mode to false in {rb_file}")
break
with open(rb_file, "w") as file:
file.writelines(lines)
@@ -51,7 +45,7 @@ def main():
print(f"Patching version: {tag}")
patch_connector(tag)
# patch_installer(tag)
patch_installer(tag)
if __name__ == "__main__":
@@ -4,7 +4,7 @@ require 'sketchup'
require 'extensions'
# Speckle connector module to enable multiplayer mode ON!
module SpeckleConnector3
module SpeckleConnector
# Version - patched by CI
CONNECTOR_VERSION = '0.0.0'
@@ -23,11 +23,11 @@ module SpeckleConnector3
PATH = File.join(PATH_ROOT, folder_name).freeze
# Run from localhost or from build files
DEV_MODE = true
DEV_MODE = false
puts("Loading Speckle Connector v#{CONNECTOR_VERSION} from #{DEV_MODE ? 'dev' : 'build'}")
unless file_loaded?(__FILE__)
ex = SketchupExtension.new('Speckle SketchUp v3', File.join(PATH, 'bootstrap'))
ex = SketchupExtension.new('Speckle SketchUp', File.join(PATH, 'bootstrap'))
ex.description = 'Speckle Connector for SketchUp'
ex.version = CONNECTOR_VERSION
ex.copyright = 'AEC Systems Ltd.'
@@ -2,8 +2,7 @@
require 'sketchup'
require 'pathname'
require 'speckle_connector_3/debug'
require_relative 'src/log/log'
require 'speckle_connector/debug'
require_relative 'src/ui/sketchup_ui'
require_relative 'src/ui/ui_controller'
require_relative 'src/commands/menu_command_handler'
@@ -13,7 +12,7 @@ require_relative 'src/states/initial_state'
require_relative 'src/commands/speckle_menu_commands'
# Speckle Connector on SketchUp to enable Multiplayer mode ON!
module SpeckleConnector3
module SpeckleConnector
SKETCHUP_VERSION = Sketchup.version.to_i
dir = __dir__.dup
@@ -24,9 +23,9 @@ module SpeckleConnector3
sketchup_ui = Ui::SketchupUi.new
ui_controller = Ui::UiController.new(sketchup_ui)
menu_commands = Commands::MenuCommandHandler.new
user_state = SpeckleConnector3::States::UserState.new({})
initial_state = SpeckleConnector3::States::InitialState.new(user_state)
app = SpeckleConnector3::App::SpeckleConnectorApp.new(menu_commands, initial_state, ui_controller)
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
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# Speckle connector module to enable multiplayer mode ON!
module SpeckleConnector3
module SpeckleConnector
# from thomthom
# https://github.com/thomthom/true-bend/blob/master/src/tt_truebend/debug.rb
@@ -15,9 +15,7 @@ module SpeckleConnector3
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') }
Dir.glob(pattern).each { |file| load(file) }
.size
end
# rubocop:enable SketchupSuggestions/FileEncoding

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

@@ -0,0 +1,31 @@
# 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
@@ -1,6 +1,6 @@
# frozen_string_literal: true
module SpeckleConnector3
module SpeckleConnector
module Actions
# State changer object.
class Action
@@ -3,7 +3,7 @@
require_relative 'action'
require_relative 'deactivate_diffing'
module SpeckleConnector3
module SpeckleConnector
module Actions
# Deactivate diffing for stream.
class ActivateDiffing < Action
@@ -15,7 +15,7 @@ module SpeckleConnector3
# @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, nil, {})
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|
@@ -2,7 +2,7 @@
require_relative 'action'
module SpeckleConnector3
module SpeckleConnector
module Actions
# Adds material to speckle state and Sketchup.
class AddMaterial < Action
@@ -2,7 +2,7 @@
require_relative 'action'
module SpeckleConnector3
module SpeckleConnector
module Actions
# Clear queue from state.
class ClearQueue < Action
@@ -4,13 +4,13 @@ require_relative 'action'
require_relative '../ext/sqlite3'
require_relative '../constants/path_constants'
module SpeckleConnector3
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, _resolve_id, _data)
def self.update_state(state, _data)
state.with_add_queue('collectPreferences', state.user_state.preferences.to_json, [])
end
end
@@ -2,16 +2,16 @@
require_relative 'action'
module SpeckleConnector3
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, _resolve_id, _data)
def self.update_state(state, _data)
versions = {
sketchup: Sketchup.version.to_i,
speckle: SpeckleConnector3::CONNECTOR_VERSION
speckle: SpeckleConnector::CONNECTOR_VERSION
}
state.with_add_queue('collectVersions', versions.to_json, [])
end
@@ -1,6 +1,6 @@
# frozen_string_literal: true
module SpeckleConnector3
module SpeckleConnector
module Actions
# Action to update connected state of application.
class Connected < Action
@@ -6,7 +6,7 @@ require_relative '../actions/save_stream'
require_relative '../actions/queue_send'
require_relative '../convertors/converter'
module SpeckleConnector3
module SpeckleConnector
module Actions
# Create stream.
class CreateStream < Action
@@ -2,13 +2,13 @@
require_relative 'action'
module SpeckleConnector3
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, _resolve_id, _data)
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|
@@ -1,11 +1,10 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative 'on_document_changed'
require_relative '../load_sketchup_model'
require_relative '../collect_preferences'
module SpeckleConnector3
module SpeckleConnector
module Actions
module Events
# Handle events that are triggered by the {AppObserver}.
@@ -26,8 +25,7 @@ module SpeckleConnector3
# 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
new_state = CollectPreferences.update_state(new_state, nil, {})
OnDocumentChanged.update_state(new_state)
CollectPreferences.update_state(new_state, {})
end
end
@@ -0,0 +1,84 @@
# 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,6 +1,6 @@
# frozen_string_literal: true
module SpeckleConnector3
module SpeckleConnector
module Actions
# This module contains actions that are performed to handle events triggered by observers in Sketchup.
module Events
@@ -3,7 +3,7 @@
require_relative 'event_action'
require_relative '../load_sketchup_model'
module SpeckleConnector3
module SpeckleConnector
module Actions
module Events
# Handle events that are triggered by the {ModelObserver}.
@@ -18,22 +18,12 @@ module SpeckleConnector3
sketchup_state = state.sketchup_state
active_path = sketchup_state.sketchup_model.active_path
observers = state.speckle_state.observers
update_entity_observers(active_path, observers)
update_object_observers(active_path, observers)
return state
end
def self.update_entity_observers(path, observers)
unless path.nil?
new_path_entities = path[-1].definition.entities
new_path_entities.add_observer(observers[ENTITIES_OBSERVER])
# attach observers to only orphan edges since face edges can be detected via face changes.
edges = new_path_entities.grep(Sketchup::Edge).filter { |edge| edge.faces.none? }
edges.each do |edge|
edge.add_observer(observers[ENTITY_OBSERVER])
edge.start.add_observer(observers[ENTITY_OBSERVER])
edge.end.add_observer(observers[ENTITY_OBSERVER])
end
end
def self.update_object_observers(path, observers)
path[-1].definition.entities.add_observer(observers[ENTITIES_OBSERVER]) unless path.nil?
end
end
@@ -4,13 +4,13 @@ require_relative 'action'
require_relative '../accounts/accounts'
require_relative 'load_saved_streams'
module SpeckleConnector3
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, _request_id, _data)
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, [])
@@ -4,9 +4,9 @@ require_relative 'action'
require_relative 'add_material'
require_relative '../constants/mat_constants'
module SpeckleConnector3
module SpeckleConnector
module Actions
# Action to initialize materials (legacy for diff colors)
# 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
@@ -7,26 +7,22 @@ require_relative '../states/sketchup_state'
require_relative '../accounts/accounts'
require_relative '../preferences/preferences'
require_relative '../constants/observer_constants'
require_relative '../ext/worker'
module SpeckleConnector3
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, instant_message_sender)
worker = SpeckleConnector3::Worker.new([])
def self.update_state(state, observers)
attach_app_observer!(observers[APP_OBSERVER])
accounts = SpeckleConnector3::Accounts.load_accounts
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,
worker, &instant_message_sender)
# This is where we attach observers to related model objects like selection, entities..
state = States::State.new(user_state_with_preferences, speckle_state, sketchup_state, false)
Actions::LoadSketchupModel.update_state(state, sketchup_state.sketchup_model)
end
@@ -2,13 +2,13 @@
require_relative 'action'
module SpeckleConnector3
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, _request_id, _data)
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, [])
@@ -3,13 +3,12 @@
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 SpeckleConnector3
module SpeckleConnector
module Actions
# Switch sketchup model wit a new one
class LoadSketchupModel < Action
@@ -23,16 +22,13 @@ module SpeckleConnector3
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))
# POC: Reconsider it when we will do caching between sessions!
# new_speckle_state = new_speckle_state.with_empty_object_references
# 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)
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))
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)
@@ -46,13 +42,11 @@ module SpeckleConnector3
# @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])
# selection = sketchup_model.selection
# selection.add_observer(observers[SELECTION_OBSERVER_NAME])
# layers = sketchup_model.layers
# layers.add_observer(observers[LAYERS_OBSERVER_NAME])
entities = sketchup_model.entities
edges = entities.grep(Sketchup::Edge)
edges.each { |edge| edge.add_observer(observers[ENTITY_OBSERVER]) }
entities.add_observer(observers[ENTITIES_OBSERVER])
sketchup_model.add_observer(observers[MODEL_OBSERVER])
# materials = sketchup_model.materials
@@ -6,7 +6,7 @@ require_relative '../accounts/accounts'
require_relative '../constants/path_constants'
require_relative '../sketchup_model/dictionary/speckle_model_dictionary_handler'
module SpeckleConnector3
module SpeckleConnector
module Actions
# When preference updated by UI.
class ModelPreferencesUpdated < Action
@@ -3,24 +3,21 @@
require_relative 'action'
require_relative 'events/app_event_action'
require_relative 'events/entities_event_action'
require_relative 'events/entity_event_action'
require_relative 'events/model_event_action'
require_relative 'events/selection_event_action'
require_relative '../constants/observer_constants'
module SpeckleConnector3
module SpeckleConnector
module Actions
# Handle events that were collected by observers
class OnEventsAction < Action
RUN_ORDER = {
APP_OBSERVER => Events::AppEventAction,
ENTITY_OBSERVER => Events::EntityEventAction,
ENTITIES_OBSERVER => Events::EntitiesEventAction,
MODEL_OBSERVER => Events::ModelEventAction,
MODEL_OBSERVER => Events::ModelEventAction
# MATERIALS_OBSERVER => Events::MaterialsEventAction,
# LAYERS_OBSERVER => Events::LayerEventAction,
# PAGES_OBSERVER => Events::PagesEventAction,
SELECTION_OBSERVER => Events::SelectionEventAction
# SELECTION_OBSERVER => Events::SelectionEventAction
}.freeze
def self.update_state(state, events)
@@ -0,0 +1,45 @@
# 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
@@ -5,7 +5,7 @@ require_relative '../states/state'
require_relative '../states/speckle_state'
require_relative '../actions/send_from_queue'
module SpeckleConnector3
module SpeckleConnector
module Actions
# Send queue from state.
class QueueSend < Action
@@ -3,15 +3,13 @@
require_relative 'action'
require_relative '../convertors/units'
require_relative '../convertors/to_native'
require_relative '../operations/receive'
require_relative '../convertors/clean_up'
module SpeckleConnector3
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, object_id)
def initialize(stream_id, base, stream_name, branch_name, branch_id, source_app)
super()
@stream_id = stream_id
@base = base
@@ -19,24 +17,16 @@ module SpeckleConnector3
@branch_name = branch_name
@branch_id = branch_id
@source_app = source_app
@object_id = object_id
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)
# content = Operations.receive(@stream_id, @object_id)
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}. ===="
@@ -4,13 +4,13 @@ require_relative 'action'
require_relative '../accounts/accounts'
require_relative 'load_saved_streams'
module SpeckleConnector3
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, _resolve_id, _data)
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)
@@ -5,7 +5,7 @@ require_relative '../accounts/accounts'
require_relative '../convertors/units'
require_relative '../convertors/converter'
module SpeckleConnector3
module SpeckleConnector
module Actions
# Action to remove stream.
# Currently it is not a state changer.
@@ -3,7 +3,7 @@
require_relative 'action'
require_relative '../accounts/accounts'
module SpeckleConnector3
module SpeckleConnector
module Actions
# Save stream.
# Currently it is not a state changer.
@@ -3,7 +3,7 @@
require_relative 'action'
require_relative '../accounts/accounts'
module SpeckleConnector3
module SpeckleConnector
module Actions
# Send already converted objects from queue if exist on stream.
class SendFromQueue < Action
@@ -4,9 +4,8 @@ require_relative 'action'
require_relative 'deactivate_diffing'
require_relative '../convertors/units'
require_relative '../convertors/to_speckle'
require_relative '../operations/send'
module SpeckleConnector3
module SpeckleConnector
module Actions
# Send selection to server.
class SendSelection < Action
@@ -18,13 +17,11 @@ module SpeckleConnector3
# @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, nil, {})
converter = Converters::ToSpeckle.new(state, @stream_id, {})
new_speckle_state, base = converter.convert_selection_to_base
id, batches = converter.serialize(base, state.user_state.preferences)
# TODO: Later active send operation.
# Operations.send(@stream_id, batches)
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
@@ -35,7 +32,7 @@ module SpeckleConnector3
new_state.with_add_queue('convertedFromSketchup', @stream_id, [
{ is_string: false, val: batches },
{ is_string: true, val: id },
{ is_string: false, val: 0 }
{ is_string: false, val: total_children_count }
])
end
end
@@ -5,7 +5,7 @@ require_relative '../ext/sqlite3'
require_relative '../accounts/accounts'
require_relative '../constants/path_constants'
module SpeckleConnector3
module SpeckleConnector
module Actions
# When preference updated by UI.
class UserPreferencesUpdated < Action
@@ -2,7 +2,7 @@
require_relative '../actions/clear_queue'
module SpeckleConnector3
module SpeckleConnector
module App
# Application for the Speckle Connector.
class SpeckleConnectorApp
@@ -28,32 +28,25 @@ module SpeckleConnector3
state.speckle_state?
end
# Attach observers to application when speckle initialized via menu commands.
def update_ui!
ui_controller.update_ui(state)
end
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
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 do |value|
instant_message_sender(value)
end
update_state!(Actions::ClearQueue)
end
def instant_message_sender(message)
ui_controller.user_interfaces.each_value do |dialog|
dialog.execute_script(message)
end
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
@@ -1,6 +1,6 @@
# frozen_string_literal: true
module SpeckleConnector3
module SpeckleConnector
module Callbacks
# Helper class to serialize messages to send dialog.
class CallbackMessage
@@ -2,15 +2,15 @@
require_relative 'command'
module SpeckleConnector3
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 binding [Ui::Binding] binding object holds commands to call
# @param action [#update_state] the action that knows how to change the state of the speckle app
def initialize(app, binding, action)
super(app, binding)
def initialize(app, action)
super(app)
@app = app
@action = action
end
@@ -3,11 +3,11 @@
require_relative 'command'
require_relative '../actions/activate_diffing'
module SpeckleConnector3
module SpeckleConnector
module Commands
# Command to activate diffing for stream.
class ActivateDiffing < Command
def _run(_resolve_id, data)
def _run(data)
stream_id = data['stream_id']
action = Actions::ActivateDiffing.new(stream_id)
app.update_state!(action)
+42
View File
@@ -0,0 +1,42 @@
# 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
@@ -0,0 +1,15 @@
# 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
@@ -2,17 +2,15 @@
require_relative 'command'
require_relative '../states/initial_state'
require_relative '../ui/legacy_binding'
require_relative '../ui/vue_view'
require_relative '../actions/initialize_speckle'
require_relative '../observers/factory'
module SpeckleConnector3
module SpeckleConnector
module Commands
# Command to initialize old Speckle UI and register it to ui_controller.
# Command to initialize Speckle UI and register it to ui_controller.
# This is the command where we show UI to user.
class InitializeSpeckle < Command
SPECKLE_LEGACY_UI = 'speckle_legacy_ui'
def dialog_title
"Speckle #{CONNECTOR_VERSION}"
end
@@ -21,34 +19,32 @@ module SpeckleConnector3
def _run
app = self.app
if !app.state.instance_of?(States::InitialState) && app.ui_controller.user_interfaces[SPECKLE_LEGACY_UI]
vue_view = app.ui_controller.user_interfaces[SPECKLE_LEGACY_UI]
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_legacy_view(app)
initialize_speckle(app)
end
# Do the actual Speckle initialization.
def initialize_speckle_legacy_view(app)
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: SPECKLE_LEGACY_UI,
dialog_id: Ui::SPECKLE_UI_ID,
htm_file: Ui::VUE_UI_HTML,
dialog_title: dialog_title,
height: 950,
width: 300
}
legacy_ui_dialog = SpeckleConnector3::Ui::Dialog.new(**dialog_specs)
legacy_binding = Ui::LegacyBinding.new(app, 'legacy_ui')
legacy_ui_dialog.bindings[Ui::SPECKLE_LEGACY_BINDING_NAME] = legacy_binding
app.ui_controller.register_ui(SPECKLE_LEGACY_UI, legacy_ui_dialog)
legacy_ui_dialog.show
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
@@ -1,6 +1,6 @@
# frozen_string_literal: true
module SpeckleConnector3
module SpeckleConnector
module Commands
# Helper class to register, handle menu and toolbar commands.
class MenuCommandHandler
@@ -9,8 +9,6 @@ module SpeckleConnector3
# @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
puts '### COMMAND CALLED BY MENU ###'
puts "Name: #{menu_text}"
command.run
end
end
@@ -4,11 +4,11 @@ require_relative 'command'
require_relative '../accounts/accounts'
require_relative '../actions/model_preference_updated'
module SpeckleConnector3
module SpeckleConnector
module Commands
# Command to update theme.
class ModelPreferencesUpdated < Command
def _run(_resolve_id, data)
def _run(data)
preference = data['preference']
new_value = data['value']
app.update_state!(Actions::ModelPreferencesUpdated.new(preference, new_value))
@@ -4,11 +4,11 @@ require_relative 'command'
require_relative '../actions/connected'
require_relative '../actions/send_from_queue'
module SpeckleConnector3
module SpeckleConnector
module Commands
# Command to notify connected.
class NotifyConnected < Command
def _run(_resolve_id, data)
def _run(data)
stream_id = data['stream_id']
app.update_state!(Actions::Connected)
app.update_state!(Actions::SendFromQueue.new(stream_id))
@@ -3,19 +3,18 @@
require_relative 'command'
require_relative '../actions/receive_objects'
module SpeckleConnector3
module SpeckleConnector
module Commands
# Command to receive objects from Speckle Server.
class ReceiveObjects < Command
def _run(_resolve_id, data)
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']
object_id = data['object_id']
action = Actions::ReceiveObjects.new(stream_id, base, stream_name, branch_name, branch_id, source_app, object_id)
action = Actions::ReceiveObjects.new(stream_id, base, stream_name, branch_name, branch_id, source_app)
app.update_state!(action)
end
end
@@ -4,11 +4,11 @@ require_relative 'command'
require_relative '../actions/remove_stream'
require_relative '../actions/load_saved_streams'
module SpeckleConnector3
module SpeckleConnector
module Commands
# Command to remove stream.
class RemoveStream < Command
def _run(_resolve_id, data)
def _run(data)
stream_id = data['stream_id']
action = Actions::RemoveStream.new(stream_id)
app.update_state!(action)
@@ -5,11 +5,11 @@ require_relative '../accounts/accounts'
require_relative '../actions/save_stream'
require_relative '../actions/load_saved_streams'
module SpeckleConnector3
module SpeckleConnector
module Commands
# Command to saved stream.
class SaveStream < Command
def _run(_resolve_id, data)
def _run(data)
stream_id = data['stream_id']
app.update_state!(Actions::SaveStream.new(stream_id))
end
@@ -3,11 +3,11 @@
require_relative 'command'
require_relative '../actions/send_selection'
module SpeckleConnector3
module SpeckleConnector
module Commands
# Command to send selection to Speckle Server.
class SendSelection < Command
def _run(_resolve_id, data)
def _run(data)
stream_id = data['stream_id']
action = Actions::SendSelection.new(stream_id)
app.update_state!(action)
@@ -0,0 +1,58 @@
# frozen_string_literal: true
require_relative 'menu_command_handler'
require_relative 'action_command'
require_relative 'initialize_speckle'
require_relative '../actions/one_click_send'
module SpeckleConnector
module Commands
# Speckle menu commands that adds them to Sketchup menu and toolbar.
class SpeckleMenuCommands
CMD_INITIALIZE_SPECKLE = :initialize_speckle
CMD_SEND_TO_SPECKLE = :send_to_speckle
CMD_RECEIVE_FROM_SPECKLE = :receive_from_speckle
# Add initial set of commands to Speckle application object and to Sketchup menu and toolbar
# @param app [App::SpeckleConnectorApp] the application object
def self.add_initial_commands!(app)
commands = app.menu_commands
ui_controller = app.ui_controller
sketchup_ui = ui_controller.sketchup_ui
speckle_menu = sketchup_ui.speckle_menu
speckle_toolbar = sketchup_ui.speckle_toolbar
commands[CMD_INITIALIZE_SPECKLE] = initialize_speckle_command(app)
commands.add_to_menu!(CMD_INITIALIZE_SPECKLE, speckle_menu)
commands.add_to_toolbar!(CMD_INITIALIZE_SPECKLE, speckle_toolbar)
# commands[CMD_SEND_TO_SPECKLE] = send_command(app)
# commands.add_to_menu!(CMD_SEND_TO_SPECKLE, speckle_menu)
# commands.add_to_toolbar!(CMD_SEND_TO_SPECKLE, speckle_toolbar)
end
def self.initialize_speckle_command(app)
cmd = MenuCommandHandler.sketchup_command(
InitializeSpeckle.new(app), 'Initialize Speckle'
)
cmd.tooltip = 'Launch Connector'
cmd.status_bar_text = 'Opens the Speckle Connector window'
cmd.small_icon = '../../img/s2logo.png'
cmd.large_icon = '../../img/s2logo.png'
cmd
end
def self.send_command(app)
cmd = MenuCommandHandler.sketchup_command(
ActionCommand.new(app, Actions::OneClickSend), 'Send to Speckle'
)
cmd.tooltip = 'Send to Speckle'
cmd.status_bar_text = 'Send to Speckle'
cmd.small_icon = '../../img/Sender.png'
cmd.large_icon = '../../img/Sender.png'
cmd.set_validation_proc { MenuCommandHandler.speckle_started(app) }
cmd
end
end
end
end
@@ -4,11 +4,11 @@ require_relative 'command'
require_relative '../accounts/accounts'
require_relative '../actions/user_preferences_updated'
module SpeckleConnector3
module SpeckleConnector
module Commands
# Command to update preferences.
class UserPreferencesUpdated < Command
def _run(_resolve_id, data)
def _run(data)
preference_hash = data['preference_hash']
preference = data['preference']
new_value = data['value']
@@ -1,16 +1,11 @@
# frozen_string_literal: true
module SpeckleConnector3
module SpeckleConnector
SPECKLE_BASE_OBJECT = 'Speckle_Base_Object'
SPECKLE_MAPPING_TOOL_SCHEMA = 'Speckle_Mapping_Tool_Schema'
SPECKLE_SCHEMA = 'Speckle_Schema'
SPECKLE_SEND_CARDS = 'Speckle_Send_Cards'
SPECKLE_RECEIVE_CARDS = 'Speckle_Receive_Cards'
SPECKLE_ID = 'speckle_id'
SPECKLE_TYPE = 'speckle_type'
APPLICATION_ID = 'application_id'
TOTAL_CHILDREN_COUNT = 'total_children_count'
CHILDREN = 'children'
PARENT = 'parent'
VALID_STREAM_IDS = 'valid_stream_ids'
@@ -1,6 +1,6 @@
# frozen_string_literal: true
module SpeckleConnector3
module SpeckleConnector
MAT_DICTIONARY = 'Speckle_Connector_Materials'
MAT_ID = 'Speckle_Connector_Material_Id'
@@ -0,0 +1,7 @@
# frozen_string_literal: true
module SpeckleConnector
APP_OBSERVER = 'SpeckleConnector::Observers::AppObserver'
ENTITIES_OBSERVER = 'SpeckleConnector::Observers::EntitiesObserver'
MODEL_OBSERVER = 'SpeckleConnector::Observers::ModelObserver'
end
@@ -4,7 +4,7 @@ require 'pathname'
require_relative 'platform_constants'
# Speckle connector module to enable multiplayer mode ON!
module SpeckleConnector3
module SpeckleConnector
dir = __dir__.dup
dir.force_encoding('UTF-8') if dir.respond_to?(:force_encoding)
HOME_PATH = (ENV['HOME']).to_s
@@ -19,6 +19,6 @@ module SpeckleConnector3
raise 'Speckle could not determine your Appdata path'
end
SPECKLE_ACCOUNTS_DB_PATH = File.join(SPECKLE_APPDATA_PATH, 'Accounts.db')
SPECKLE_CONFIG_DB_PATH = File.join(SPECKLE_APPDATA_PATH, 'DUI3Config.db')
SPECKLE_CONFIG_DB_PATH = File.join(SPECKLE_APPDATA_PATH, 'Config.db')
SPECKLE_TEST_DB_PATH = File.join(SPECKLE_APPDATA_PATH, 'sketchup_test.db')
end
@@ -1,7 +1,7 @@
# frozen_string_literal: true
# rubocop:disable Style/Documentation
module SpeckleConnector3
module SpeckleConnector
host_os = RbConfig::CONFIG['host_os']
OS_WIN = :windows
OS_MAC = :macos
@@ -2,7 +2,7 @@
require_relative '../speckle_objects/geometry/length'
module SpeckleConnector3
module SpeckleConnector
COMBINE_FACES_BY_MATERIAL = :combine_faces_by_material
INCLUDE_ENTITY_ATTRIBUTES = :include_entity_attributes
INCLUDE_FACE_ENTITY_ATTRIBUTES = :include_face_entity_attributes
@@ -11,13 +11,6 @@ module SpeckleConnector3
INCLUDE_COMPONENT_ENTITY_ATTRIBUTES = :include_component_entity_attributes
MERGE_COPLANAR_FACES = :merge_coplanar_faces
ENTITY_KEYS_FOR_INCLUDING_ATTRIBUTES = {
Sketchup::ComponentInstance => INCLUDE_COMPONENT_ENTITY_ATTRIBUTES,
Sketchup::Group => INCLUDE_GROUP_ENTITY_ATTRIBUTES,
Sketchup::Face => INCLUDE_FACE_ENTITY_ATTRIBUTES,
Sketchup::Edge => INCLUDE_EDGE_ENTITY_ATTRIBUTES
}.freeze
LEVEL_SHIFT_VALUE = SpeckleObjects::Geometry.length_to_native(1.5, 'm')
DEFAULT_MODEL_PREFERENCES = {
@@ -0,0 +1,19 @@
# frozen_string_literal: true
module SpeckleConnector
BASE_OBJECT = 'Base'
OBJECTS_BUILTELEMENTS_VIEW3D = 'Objects.BuiltElements.View:Objects.BuiltElements.View3D'
OBJECTS_GEOMETRY_LINE = 'Objects.Geometry.Line'
OBJECTS_GEOMETRY_POLYLINE = 'Objects.Geometry.Polyline'
OBJECTS_GEOMETRY_MESH = 'Objects.Geometry.Mesh'
OBJECTS_GEOMETRY_BREP = 'Objects.Geometry.Brep'
OBJECTS_OTHER_BLOCKINSTANCE = 'Objects.Other.BlockInstance'
OBJECTS_OTHER_BLOCKINSTANCE_FULL = 'Objects.Other.Instance:Objects.Other.BlockInstance'
OBJECTS_OTHER_INSTANCE = 'Objects.Other.Instance:Objects.Other.Instance'
OBJECTS_OTHER_REVIT_REVITINSTANCE = 'Objects.Other.Revit.RevitInstance'
OBJECTS_OTHER_BLOCKDEFINITION = 'Objects.Other.BlockDefinition'
OBJECTS_OTHER_RENDERMATERIAL = 'Objects.Other.RenderMaterial'
end
@@ -5,12 +5,10 @@ require 'securerandom'
# rubocop:enable SketchupPerformance/OpenSSL
require 'digest'
require_relative 'converter'
require_relative '../speckle_objects/base'
require_relative '../speckle_objects/object_reference'
require_relative '../speckle_entities/speckle_entity'
require_relative '../relations/many_to_one_relation'
module SpeckleConnector3
module SpeckleConnector
module Converters
# Serializer of the base object.
# Responsible to create id (hash) of the objects by holding their lineage and detaching relationships.
@@ -18,17 +16,23 @@ module SpeckleConnector3
# @return [Integer] default chunk size the determine splitting base prop into chucks
attr_reader :default_chunk_size
attr_reader :object_references
# @return [String] stream id to send conversion
attr_reader :stream_id
def initialize(preferences, default_chunk_size = 1000)
attr_accessor :speckle_state
# @param stream_id [String] stream id to send conversion
def initialize(speckle_state, stream_id, preferences, default_chunk_size = 1000)
@speckle_state = speckle_state
@stream_id = stream_id
@preferences = preferences
@default_chunk_size = default_chunk_size
@detach_lineage = []
@lineage = []
@family_tree = {}
@family_tree_relation = Relations::ManyToOneRelation.new
@closure_table = {}
@objects = {}
@object_references = {}
end
# @param base [Object] top base object to populate all children and their relationship
@@ -39,15 +43,40 @@ module SpeckleConnector3
id
end
# @param base [Object] base object to populate all children and their relationship
def total_children_count(id)
@objects[id][:totalChildrenCount]
end
# @param base_and_entities [Object] base object to populate all children and their relationship
# rubocop:disable Metrics/MethodLength
def traverse_base(base)
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/AbcSize
def traverse_base(base_and_entities)
base, entities = base_and_entities
# 1. Create random string for lineage tracking.
@lineage.append(SecureRandom.hex)
# 2. Get last item from detach_lineage array
is_detached = @detach_lineage.pop
# unless entities.nil?
# is_sent_before = entities.all? do |entity|
# check_base_available_on_state(entity, speckle_state)
# end
# if is_sent_before
# speckle_entity = speckle_state.speckle_entities[entities.first.persistent_id]
# ref_object = detach_helper(speckle_entity.id)
# parent = @lineage[-1]
# unless @family_tree[parent].nil?
# @family_tree[parent] = @family_tree[parent].merge(speckle_entity.speckle_object[:__closure])
# end
# @objects[speckle_entity.id] = ref_object if is_detached
# return speckle_entity.id, ref_object
# end
# end
# 3. Initialize traversed base object that will be filled with traversed values or
# traversed base objects as props.
traversed_base = SpeckleObjects::Base.new(speckle_type: base[:speckle_type], id: '')
@@ -56,6 +85,7 @@ module SpeckleConnector3
traversed_base.delete(:applicationId)
# 4. Iterate all entries (key, value) of the base {Base > Hash} object
# speckle_state = traverse_base_props(base, traversed_base)
traverse_base_props(base, traversed_base)
# this is where all props are done for current `traversed_base`
@@ -68,6 +98,9 @@ module SpeckleConnector3
end
end
# 6. Add total children count
traversed_base[:totalChildrenCount] = closure.keys.length
# 7. Finally create id
id = get_id(traversed_base)
@@ -75,7 +108,7 @@ module SpeckleConnector3
traversed_base[:id] = id
# 9. Update __closure table on the traversed base
unless closure.empty?
unless traversed_base[:totalChildrenCount].nil?
@closure_table[id] = closure
traversed_base[:__closure] = closure unless closure.empty?
end
@@ -83,15 +116,19 @@ module SpeckleConnector3
# 10. Save object string if detached
@objects[id] = traversed_base if is_detached
if traversed_base[:applicationId]
@object_references[traversed_base[:applicationId].to_s] = SpeckleObjects::ObjectReference.new(
id, traversed_base[:applicationId].to_s, traversed_base[:__closure]
)
if @preferences[:user][:register_speckle_entity] && !entities.nil?
entities.uniq.each do |entity|
speckle_entity = create_or_update_speckle_entity(entity, id, traversed_base)
@speckle_state = speckle_state.with_speckle_entity(speckle_entity)
end
end
return id, traversed_base
end
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
@@ -109,15 +146,16 @@ module SpeckleConnector3
next
end
# 3.3. Check prop needs to split into chunks
chunked_detach_match = prop.match(/^@\((\d*)\)/)
# 3.4. Determine prop is dynamically detached or not
# 3.3. Determine prop is dynamically detached or not
is_detach_prop = prop[0] == '@'
is_dynamically_detached = prop[0] == '@' && prop.length > 2 && prop[1] == '@'
prop = prop[2..-1] if is_dynamically_detached
# 3.4. Check prop needs to split into chunks
chunked_detach_match = prop.match(/^@\((\d*)\)/)
# 3.5. If split chunk is needed and prop value is array, then run chunking process
if value.is_a?(Array) && chunked_detach_match
if value.is_a?(Array) && !base_and_entities?(value) && chunked_detach_match
# 3.5.1. Determine chunk size, get it from prop if defined. ex: '@(31250)faces' -> 31250 = chunk size
chunk_size = chunked_detach_match[1] == '' ? default_chunk_size : chunked_detach_match[1].to_i
@@ -164,24 +202,14 @@ module SpeckleConnector3
next
end
# 3.6 we cleanup the semantic @ or @@ we used so far for detaching or chunking
if is_dynamically_detached
prop = prop[2..-1]
else
prop = prop[1..-1] if is_detach_prop
end
child = traverse_value(value, is_detach_prop)
is_base = value.is_a?(Hash) && !value[:speckle_type].nil?
is_base = (value.is_a?(Hash) && !value[:speckle_type].nil?) ||
(base_and_entities?(value) && value[0].is_a?(Hash) && !value[0][:speckle_type].nil?)
# 3.7. traverse value according to value is a speckle object or not
# 3.6. traverse value according to value is a speckle object or not
traversed_base[prop] = if is_base
if child[:referencedId] && child[:speckle_type] == 'reference'
is_detach_prop ? detach_helper(child[:referencedId]) : child
else
is_detach_prop ? detach_helper(child[:id]) : child
end
is_detach_prop ? detach_helper(child[:id]) : child
else
child
end
@@ -193,6 +221,16 @@ module SpeckleConnector3
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# Whether value has a pattern [<converted>, [<entity>, <entity>, ... <entity>]] or not.
def base_and_entities?(value)
is_array = value.is_a?(Array)
return false unless is_array
return false unless is_array && value.length == 2
value[1].all? { |v| v.is_a?(Sketchup::Entity) }
end
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
@@ -202,8 +240,8 @@ module SpeckleConnector3
# 1. Return same value if value is primitive type (string, numeric, boolean)
return value unless value.is_a?(Hash) || value.is_a?(Array)
# 2. For pure arrays
if value.is_a?(Array)
# 2. For pure arrays (Without referencing any Sketchup Entity)
if value.is_a?(Array) && !base_and_entities?(value)
# 2.1. If it is not detached then iterate array by traversing with their value
unless is_detach
@@ -217,17 +255,7 @@ module SpeckleConnector3
# 2.2. If it is detached than collect them into detached_list
detached_list = []
value.each do |el|
if el.is_a?(SpeckleObjects::ObjectReference)
if el.closure
el.closure.each_key do |k|
detach_helper(k)
end
end
detached_list.append(detach_helper(el.referenced_id))
next
end
if el.is_a?(Hash) && !el[:speckle_type].nil?
if (el.is_a?(Hash) && !el[:speckle_type].nil?) || base_and_entities?(el)
@detach_lineage.append(is_detach)
id, _traversed_base = traverse_base(el)
detached_list.append(detach_helper(id))
@@ -239,21 +267,11 @@ module SpeckleConnector3
return detached_list
end
# 3. ObjectReference
if value.is_a?(SpeckleObjects::ObjectReference)
if value.closure
value.closure.each_key do |k|
detach_helper(k)
end
end
return detach_helper(value.referenced_id)
end
# 4. Hash
# 3. Hash
return value if value.is_a?(Hash) && value[:speckle_type].nil?
# 5. Base objects
if value.is_a?(Hash) && !value[:speckle_type].nil?
# 4. Base objects
if (value.is_a?(Hash) && !value[:speckle_type].nil?) || base_and_entities?(value)
@detach_lineage.append(is_detach)
_id, traversed_base = traverse_base(value)
return traversed_base
@@ -285,17 +303,13 @@ module SpeckleConnector3
}
end
# @param traversed_base [SpeckleConnector3::SpeckleObjects::Base] traversed base object.
# @param traversed_base [SpeckleConnector::SpeckleObjects::Base] traversed base object.
def get_id(traversed_base)
Digest::MD5.hexdigest(traversed_base.to_json)
end
def batch_objects
@objects
end
# rubocop:disable Metrics/MethodLength
def batch_json_objects(max_batch_size_mb = 1)
def batch_objects(max_batch_size_mb = 1)
max_size = 1000 * 1000 * max_batch_size_mb
batches = []
batch = '['
@@ -319,6 +333,32 @@ module SpeckleConnector3
batches
end
# rubocop:enable Metrics/MethodLength
# @param entity [Sketchup::Entity] source entity object
# @param speckle_state [States::SpeckleState] the current speckle state of the {States::State}
def check_base_available_on_state(entity, speckle_state)
is_exist = speckle_state.speckle_entities.keys.include?(entity.persistent_id)
return is_exist unless is_exist
speckle_state.speckle_entities[entity.persistent_id].valid_stream_ids.include?(stream_id)
end
# Creates or updates speckle entity.
# If speckle entity exist in state, creates new one by updating old one.
# Else creates new one
# @return [SpeckleEntity] speckle entity that collects both speckle and sketchup information.
def create_or_update_speckle_entity(entity, id, traversed_base)
if speckle_state.speckle_entities.keys.include?(entity.persistent_id)
speckle_state.speckle_entities[entity.persistent_id].with_valid_stream_id(stream_id)
else
children = traversed_base[:__closure].nil? ? {} : traversed_base[:__closure]
speckle_entity = SpeckleEntities::SpeckleEntity.new(entity, id, entity.persistent_id,
traversed_base[:speckle_type],
children.keys, [stream_id])
speckle_entity.write_initial_base_data
speckle_entity
end
end
end
end
end
@@ -3,7 +3,7 @@
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
module SpeckleConnector3
module SpeckleConnector
module Converters
# CleanUp is a plugin developed by [Thomas Thomassen](https://github.com/thomthom).
module CleanUp
@@ -11,41 +11,12 @@ module SpeckleConnector3
# @param entities [Sketchup::Entities] entities to remove edges between that make entities coplanar.
# @note Merging coplanar faces idea originated from [CleanUp](https://github.com/thomthom/cleanup) plugin
# which is developed by [Thomas Thomassen](https://github.com/thomthom).
def self.merge_coplanar_entities(entities)
def self.merge_coplanar_faces(entities)
edges = []
faces = entities.collect { |entity| entity if entity.is_a? Sketchup::Face }.compact
faces = merged_faces(faces)
faces.each { |face| face.edges.each { |edge| edges << edge } }
edges.uniq!
edges.each { |edge| remove_edge_have_coplanar_faces(edge, faces, false) }
# Remove remaining orphan edges
edges.reject(&:deleted?).select { |edge| edge.faces.empty? }.each(&:erase!)
merged_faces(faces)
end
def self.merge_coplanar_faces(faces)
edges = []
faces = faces.reject(&:deleted?)
faces.each { |face| face.edges.each { |edge| edges << edge } }
edges.uniq!
edges.each { |edge| remove_edge_have_coplanar_faces(edge) }
# Remove remaining orphan edges
# the commented out code throws an exception over deleted elements
# even if they are rejected by deleted? flag
# edges.reject(&:deleted?).select { |edge| edge.faces.empty? }.each(&:erase!)
edges.each do |edge|
next if edge.deleted?
begin
edge.erase! if edge.faces.empty?
rescue Sketchup::DeletedEntityError
# Ignore deleted edge
end
end
merged_faces(faces)
end
@@ -62,34 +33,43 @@ module SpeckleConnector3
# - Whether UV texture map is aligned between faces or not.
# - Finally, if faces are coplanar by correcting these checks, then removes edge from Sketchup.active_model.
# @param edge [Sketchup::Edge] edge to check.
# @param faces [Array<Sketchup::Face>] scoped faces to check 'edge.faces' both (first and second)
# belongs to this faces or not. If any of this faces does not involve this scoped faces, then do not delete.
# @param ignore_materials [Boolean] whether ignore materials or not.
# Returns true if the given edge separating two coplanar faces.
# Return false otherwise.
def self.remove_edge_have_coplanar_faces(edge)
# rubocop:disable Metrics/AbcSize
def self.remove_edge_have_coplanar_faces(edge, faces, ignore_materials)
return false unless edge.valid? && edge.is_a?(Sketchup::Edge)
return false unless edge.faces.size == 2
# Check scoped faces have this edges
if edge.faces.size == 2
is_first = faces.include?(edge.faces[0])
is_second = faces.include?(edge.faces[1])
return false unless is_first && is_second
end
face_1, face_2 = edge.faces
begin
return false unless face_1.normal.samedirection?(face_2.normal)
return false if face_duplicate?(face_1, face_2)
# Check for troublesome faces which might lead to missing geometry if merged.
return false unless edge_safe_to_merge?(edge)
return false if face_duplicate?(face_1, face_2)
# Check for troublesome faces which might lead to missing geometry if merged.
return false unless edge_safe_to_merge?(edge)
# Check materials match.
unless ignore_materials
return false unless (face_1.material == face_2.material) && (face_1.back_material == face_2.back_material)
# Check faces are coplanar or not.
return false unless faces_coplanar?(face_1, face_2)
edge.erase!
true
rescue StandardError => e
puts "Failed to merge coplanar faces by removing edge with error: #{e}"
false
# Verify UV mapping match.
return false if !face_1.material.nil? && !continuous_uv?(face_1, face_2, edge) && face_1.material.texture.nil?
end
# Check faces are coplanar or not.
return false unless faces_coplanar?(face_1, face_2)
edge.erase!
true
end
# rubocop:enable Metrics/AbcSize
# Determines if two faces are overlapped.
def self.face_duplicate?(face_1, face_2, overlapping: false)
@@ -1,10 +1,10 @@
# frozen_string_literal: true
module SpeckleConnector3
module SpeckleConnector
module Converters
# Helper class to convert geometries between server and Sketchup.
class Converter
# @return [States::State] the current state of the {SpeckleConnector3::App}
# @return [States::State] the current state of the {SpeckleConnector::App}
attr_reader :state
# @return [States::SpeckleState] the current speckle state of the {States::State}
@@ -21,14 +21,14 @@ module SpeckleConnector3
attr_accessor :definitions
# @param state [States::State] the current state of the {SpeckleConnector3::App}
# @param state [States::State] the current state of the {SpeckleConnector::App}
def initialize(state, stream_id)
@state = state
@speckle_state = state.speckle_state
@sketchup_model = state.sketchup_state.sketchup_model
@stream_id = stream_id
su_unit = state.sketchup_state.length_units
@units = Converters::SKETCHUP_UNITS[su_unit]
@units = Converters::SKETCHUP_UNITS[su_unit]
@definitions = {}
end
end
@@ -2,29 +2,18 @@
require_relative 'converter'
require_relative '../constants/type_constants'
require_relative '../speckle_entities/speckle_entity'
require_relative '../speckle_objects/gis/polygon_element'
require_relative '../speckle_objects/gis/line_element'
require_relative '../speckle_objects/other/transform'
require_relative '../speckle_objects/other/render_material'
require_relative '../speckle_objects/other/block_definition'
require_relative '../speckle_objects/other/block_instance'
require_relative '../speckle_objects/other/display_value'
require_relative '../speckle_objects/revit/revit_instance'
require_relative '../speckle_objects/other/revit/revit_instance'
require_relative '../speckle_objects/geometry/point'
require_relative '../speckle_objects/geometry/line'
require_relative '../speckle_objects/geometry/polycurve'
require_relative '../speckle_objects/geometry/arc'
require_relative '../speckle_objects/geometry/circle'
require_relative '../speckle_objects/geometry/mesh'
require_relative '../speckle_objects/built_elements/view3d'
require_relative '../speckle_objects/built_elements/network'
require_relative '../speckle_objects/speckle/core/models/collection'
require_relative '../speckle_objects/speckle/core/models/gis_layer_collection'
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
require_relative '../ui_data/report/conversion_result'
module SpeckleConnector3
module SpeckleConnector
module Converters
# Converts sketchup entities to speckle objects.
# rubocop:disable Metrics/ClassLength
@@ -35,55 +24,31 @@ module SpeckleConnector3
# @return [String] source application of received object that will be converted to native
attr_reader :source_app
attr_reader :converted_faces
attr_reader :converted_entities
attr_reader :conversion_results
def initialize(state, stream_id, stream_name, branch_name, source_app, model_card_id)
super(state, stream_id, model_card_id)
def initialize(state, stream_id, stream_name, branch_name, source_app)
super(state, stream_id)
@stream_name = stream_name
@branch_name = branch_name
@source_app = source_app.downcase
@converted_faces = []
@converted_entities = []
@conversion_results = []
end
# Module aliases
GEOMETRY = SpeckleObjects::Geometry
OTHER = SpeckleObjects::Other
REVIT = SpeckleObjects::Revit
BUILTELEMENTS = SpeckleObjects::BuiltElements
GIS = SpeckleObjects::GIS
# Class aliases
POINT = GEOMETRY::Point
LINE = GEOMETRY::Line
POLYCURVE = GEOMETRY::Polycurve
ARC = GEOMETRY::Arc
CIRCLE = GEOMETRY::Circle
MESH = GEOMETRY::Mesh
BLOCK_DEFINITION = OTHER::BlockDefinition
BLOCK_INSTANCE = OTHER::BlockInstance
REVIT_INSTANCE = REVIT::Other::RevitInstance
REVIT_WALL = BUILTELEMENTS::RevitWall
REVIT_INSTANCE = OTHER::Revit::RevitInstance
RENDER_MATERIAL = OTHER::RenderMaterial
DISPLAY_VALUE = OTHER::DisplayValue
VIEW3D = BUILTELEMENTS::View3d
POLYGON_ELEMENT = GIS::PolygonElement
LINE_ELEMENT = GIS::LineElement
COLLECTION = SpeckleObjects::Speckle::Core::Models::Collection
GIS_LAYER_COLLECTION = SpeckleObjects::Speckle::Core::Models::GisLayerCollection
BASE_OBJECT_PROPS = %w[applicationId id speckle_type].freeze
BASE_OBJECT_PROPS = %w[applicationId id speckle_type totalChildrenCount].freeze
CONVERTABLE_SPECKLE_TYPES = %w[
Objects.Geometry.Line
Objects.Geometry.Polyline
Objects.Geometry.Polycurve
Objects.Geometry.Arc
Objects.Geometry.Circle
Objects.Geometry.Mesh
Objects.Geometry.Brep
Objects.Other.BlockInstance
@@ -91,93 +56,44 @@ module SpeckleConnector3
Objects.Other.BlockDefinition
Objects.Other.RenderMaterial
Objects.Other.Instance:Objects.Other.BlockInstance
Objects.BuiltElements.View:Objects.BuiltElements.View3D
Objects.BuiltElements.Wall:Objects.BuiltElements.Revit.RevitWall
Objects.BuiltElements.Network
Objects.GIS.PolygonElement
Objects.GIS.LineElement
Speckle.Core.Models.Collection
Speckle.Core.Models.Collection:Objects.GIS.RasterLayer
Speckle.Core.Models.Collection:Objects.GIS.VectorLayer
].freeze
def from_revit
@from_revit ||= source_app.include?('revit')
end
def from_rhino
@from_rhino ||= source_app.include?('rhino')
end
def from_sketchup
@from_sketchup ||= source_app.include?('sketchup')
end
def from_qgis
@from_qgis ||= source_app.include?('qgis')
end
# ReceiveObjects action call this method by giving everything that comes from server.
# Upcoming object is a referencedObject of selected commit to receive.
# UI is responsible currently to fetch objects from ObjectLoader module by calling getAndConstruct method.
# @param obj [Object] speckle commit object.
def receive_commit_object(obj)
unless from_revit
# Create layers and it's folders from layers relation on the model collection.
SpeckleObjects::Relations::Layers.to_native(obj, source_app, sketchup_model)
end
# By default entities to fill is sketchup model's entities.
@entities_to_fill = sketchup_model.entities
# Navigate to branch entities if commit doesn't come from sketchup
unless from_sketchup
@branch_definition = branch_definition
@entities_to_fill = @branch_definition.entities
end
# First create layers on the sketchup before starting traversing
# @Named Views are exception here. It does not mean a layer. But it is anti-pattern for now.
filtered_layer_containers = obj.keys.filter_map { |key| key if key.start_with?('@') && key != '@Named Views' }
create_layers(filtered_layer_containers, sketchup_model.layers) unless from_revit
# Convert views to sketchup scenes
SpeckleObjects::BuiltElements::View3d.to_native(obj, sketchup_model)
# Get default commit layer from sketchup model which will be used as fallback
default_commit_layer = sketchup_model.layers.layers.find { |layer| layer.display_name == '@Untagged' }
traverse_commit_object(obj, default_commit_layer, @entities_to_fill)
@entities_to_fill = entities_to_fill(obj)
traverse_commit_object(obj, sketchup_model.layers, default_commit_layer, @entities_to_fill)
create_levels_from_section_planes
check_hiding_layers_needed
try_create_instance
@state
end
# Creating instance from @branch_definition only available for non-sketchup commits since we wrap commits
# under instance.
# There is also another use case that maybe definition is exist in file but user might be deleted it before.
# If this is the case we can add instance by checking number of instances.
# rubocop:disable Style/GuardClause
def try_create_instance
if !from_sketchup && (!@is_update_commit || @branch_definition.instances.empty?)
instance = sketchup_model.entities.add_instance(@branch_definition, Geom::Transformation.new)
@converted_entities.append(instance)
BLOCK_INSTANCE.align_instance_axes(instance) if from_qgis
end
end
# rubocop:enable Style/GuardClause
def levels_layer
@levels_layer ||= sketchup_model.layers.add('Levels')
end
def clear_levels
instances = @entities_to_fill.grep(Sketchup::ComponentInstance)
instances.each do |instance|
speckle_type = instance.get_attribute(SPECKLE_BASE_OBJECT, 'speckle_type')
next if speckle_type.nil?
sketchup_model.definitions.remove(instance.definition) if speckle_type == OBJECTS_BUILTELEMENTS_REVIT_LEVEL
end
end
# Create levels from section planes that already created for this commit object.
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
def create_levels_from_section_planes
clear_levels if @is_update_commit
return unless from_revit
section_planes = @entities_to_fill.grep(Sketchup::SectionPlane)
@@ -189,9 +105,7 @@ module SpeckleConnector3
section_planes.each do |section_plane|
level_name = "#{@definition_name}-#{section_plane.name}"
definition = sketchup_model.definitions.add(level_name)
instance = @entities_to_fill.add_instance(definition, Geom::Transformation.new)
att = section_plane.attribute_dictionary(SPECKLE_BASE_OBJECT).to_h
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.set_hash(instance, att)
@entities_to_fill.add_instance(definition, Geom::Transformation.new)
elevation = section_plane.bounds.center.z
c1_e = Geom::Point3d.new(c_1.x, c_1.y, elevation - LEVEL_SHIFT_VALUE)
c2_e = Geom::Point3d.new(c_2.x, c_2.y, elevation - LEVEL_SHIFT_VALUE)
@@ -208,17 +122,8 @@ module SpeckleConnector3
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# @return [Sketchup::ComponentDefinition] branch definition to fill objects in it.
def branch_definition
@definition_name = "#{@branch_name}-#{@stream_name}"
definition = sketchup_model.definitions.find { |d| d.name == @definition_name }
@is_update_commit = !definition.nil?
definition = sketchup_model.definitions.add(@definition_name) if definition.nil?
definition
end
def entities_to_fill(_obj)
return sketchup_model.entities unless from_revit
return sketchup_model.entities if from_sketchup
@definition_name = "#{@branch_name}-#{@stream_name}"
definition = sketchup_model.definitions.find { |d| d.name == @definition_name }
@@ -226,7 +131,7 @@ module SpeckleConnector3
definition = sketchup_model.definitions.add(@definition_name)
sketchup_model.entities.add_instance(definition, Geom::Transformation.new)
end
definition
definition.entities
end
LAYERS_WILL_BE_HIDDEN = [
@@ -264,14 +169,62 @@ module SpeckleConnector3
['Objects.BuiltElements.Revit.Parameter'].include?(obj['speckle_type'])
end
# Create actual Sketchup layers from layer_paths that taken from Speckle base object.
# @param layer_paths [Array<String>] layer paths to decompose it to folders and it's layers.
# @param folder [Sketchup::Layers, Sketchup::LayerFolder] folder to create folders and layers under it.
def create_layers(layer_paths, folder)
# Strip leading '@'
layers_with_folders = layer_paths.map { |layer| layer[1..-1] }
# Split layer_paths according to having parent folder or not.
layers_with_head_folder, headless_layers = layers_with_folders.partition { |layer| layer.include?('::') }
# Create array of array that split with '::'
folder_layer_arrays = layers_with_head_folder.collect { |folder_layer| folder_layer.split('::') }
# Add headless layers into `Sketchup.active_model.layers`
create_headless_layers(headless_layers, folder)
# Create layers that have parent folder(s)- this method is recursive until all tree is created.
create_folder_layers(folder_layer_arrays, folder)
end
# @param headless_layers [Array<String>] headless layer names.
# @param folder [Sketchup::Layers, Sketchup::LayerFolder] layer folder to create commit layers under it.
def create_headless_layers(headless_layers, folder)
headless_layers.each do |layer_name|
# Add layer first to the layers object of sketchup model.
layer = sketchup_model.layers.add(layer_name)
folder.add_layer(layer) unless folder.layers.any? { |l| l.display_name == layer_name }
end
end
# Create layers with it's parent folders.
# @param folder [Sketchup::LayerFolder] layer folder to create commit layers under it.
def create_folder_layers(folder_layer_arrays, folder)
folder_layer_arrays.each do |folder_layer_array|
create_folder_layer(folder_layer_array, folder)
end
end
# Create layers that have parent folder(s)- this method is recursive (self-caller) until all tree is created.
def create_folder_layer(folder_array, folder)
if folder_array.length > 1
# add folder if it is not exist.
folder.add_folder(folder_array[0]) unless folder.folders.any? { |f| f.display_name == folder_array[0] }
new_folder = folder.folders.find { |f| f.display_name == folder_array[0] }
create_folder_layer(folder_array[1..-1], new_folder)
else
# Add layer first to the layers object of sketchup model.
layer = sketchup_model.layers.add(folder_array[0])
folder.add_layer(layer) unless folder.layers.any? { |l| l.display_name == layer }
end
end
# Traversal method to create Sketchup objects from upcoming base object.
# @param obj [Hash, Array] object might be source base object or it's sub objects, because this method is a
# self-caller method means that call itself according to conditions inside of it.
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def traverse_commit_object(obj, layer, entities)
def traverse_commit_object(obj, commit_folder, layer, entities)
if convertible_to_native?(obj)
@state, _converted_entities = convert_to_native(@state, obj, layer, entities)
@state = convert_to_native(@state, obj, layer, entities)
elsif obj.is_a?(Hash) && obj.key?('speckle_type')
return if ignored_speckle_type?(obj)
@@ -279,23 +232,57 @@ module SpeckleConnector3
# puts(">>> Found #{obj['speckle_type']}: #{obj['id']}. Continuing traversal.")
props = obj.keys.filter_map { |key| key unless key.start_with?('_') }
props.each do |prop|
traverse_commit_object(obj[prop], layer, entities)
layer_path = prop if prop.start_with?('@') && obj[prop].is_a?(Array)
layer = find_layer(layer_path, commit_folder, layer)
traverse_commit_object(obj[prop], commit_folder, layer, entities)
end
else
# puts(">>> Found #{obj['speckle_type']}: #{obj['id']} with displayValue.")
@state, _converted_entities = convert_to_native(@state, obj, layer, entities)
@state = convert_to_native(@state, obj, layer, entities)
end
elsif obj.is_a?(Hash)
obj.each_value { |value| traverse_commit_object(value, layer, entities) }
obj.each_value { |value| traverse_commit_object(value, commit_folder, layer, entities) }
elsif obj.is_a?(Array)
obj.each { |value| traverse_commit_object(value, layer, entities) }
obj.each { |value| traverse_commit_object(value, commit_folder, layer, entities) }
end
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# Find layer of the Speckle object by checking iteratively into folder.
# @param layer_path [String] complete layer_path to retrieve
# @param folder [Sketchup::LayerFolder, Sketchup::Layers] entry folder to search layer
# @param fallback_layer [Sketchup::Layer] fallback layer to assign object later if any error occur.
# @return [Sketchup::Layer] layer according to path
# @example
# "@folder_1::folder_2::layer_1"
# # it will return the layer object which has display name as `layer_1`.
def find_layer(layer_path, folder, fallback_layer)
begin
# Split folders and it's tail layer (last one is layer, others are folders.)
layer_path_array = layer_path[1..-1].split('::')
# Get sub folders as array, might be empty if `layer_path_array` has only 1 entry
sub_folders = layer_path_array.length > 1 ? layer_path_array[0..-2] : []
# Get exact layer name from last entry
layer_name = layer_path_array.last
# Iterate sub folders to find new sub folder to switch it.
# It help to search in the tree by switching the target search folder.
# Finally we can reach the layer name.
sub_folders.each do |sub_folder|
# Try to find sub folder into source folder passes by argument
s_f = folder.folders.find { |f| f.display_name == sub_folder }
# Switch source folder if any exist
folder = s_f unless s_f.nil?
end
# Find finally the layer into related folder
folder.layers.find { |l| l.display_name == layer_name }
rescue StandardError
return fallback_layer
end
end
def speckle_object_to_native(obj)
return DISPLAY_VALUE.method(:to_native) unless obj['displayValue'].nil? && obj['@displayValue'].nil?
return DISPLAY_VALUE.method(:to_native) unless obj['displayValue'].nil?
SPECKLE_OBJECT_TO_NATIVE[obj['speckle_type']]
end
@@ -303,25 +290,13 @@ module SpeckleConnector3
SPECKLE_OBJECT_TO_NATIVE = {
OBJECTS_GEOMETRY_LINE => LINE.method(:to_native),
OBJECTS_GEOMETRY_POLYLINE => LINE.method(:to_native),
OBJECTS_GEOMETRY_POLYCURVE => POLYCURVE.method(:to_native),
OBJECTS_GEOMETRY_ARC => ARC.method(:to_native),
OBJECTS_GEOMETRY_CIRCLE => CIRCLE.method(:to_native),
OBJECTS_GEOMETRY_MESH => MESH.method(:to_native),
OBJECTS_GEOMETRY_BREP => MESH.method(:to_native),
OBJECTS_OTHER_BLOCKDEFINITION => BLOCK_DEFINITION.method(:to_native),
OBJECTS_OTHER_BLOCKINSTANCE => BLOCK_INSTANCE.method(:to_native),
OBJECTS_OTHER_BLOCKINSTANCE_FULL => BLOCK_INSTANCE.method(:to_native),
OBJECTS_OTHER_REVIT_REVITINSTANCE => REVIT_INSTANCE.method(:to_native),
OBJECTS_OTHER_RENDERMATERIAL => RENDER_MATERIAL.method(:to_native),
OBJECTS_BUILTELEMENTS_VIEW3D => VIEW3D.method(:to_native),
OBJECTS_BUILTELEMENTS_REVIT_WALL => REVIT_WALL.method(:to_native),
OBJECTS_BUILTELEMENTS_REVIT_DIRECTSHAPE => BUILTELEMENTS::Revit::DirectShape.method(:to_native),
OBJECTS_BUILTELEMENTS_NETWORK => BUILTELEMENTS::Network.method(:to_native),
OBJECTS_GIS_POLYGONELEMENT => POLYGON_ELEMENT.method(:to_native),
OBJECTS_GIS_LINEELEMENT => LINE_ELEMENT.method(:to_native),
SPECKLE_CORE_MODELS_COLLECTION => COLLECTION.method(:to_native),
SPECKLE_CORE_MODELS_COLLECTION_RASTER_LAYER => GIS_LAYER_COLLECTION.method(:to_native),
SPECKLE_CORE_MODELS_COLLECTION_VECTOR_LAYER => GIS_LAYER_COLLECTION.method(:to_native)
OBJECTS_OTHER_RENDERMATERIAL => RENDER_MATERIAL.method(:to_native)
}.freeze
# @param state [States::State] state of the speckle application
@@ -333,9 +308,6 @@ module SpeckleConnector3
# Call 'to_native' method by passing this method itself to handle nested 'to_native' conversions.
# It returns updated state and converted entities.
state, converted_entities = to_native_method.call(state, obj, layer, entities, &convert_to_native)
@converted_entities += converted_entities
faces = converted_entities.select { |e| e.is_a?(Sketchup::Face) }
@converted_faces += faces if faces.any?
if from_revit
# Create levels as section planes if they exists
create_levels(state, obj)
@@ -343,26 +315,11 @@ module SpeckleConnector3
create_layers_from_categories(state, obj, converted_entities)
end
# Create speckle entities from sketchup entities to achieve continuous traversal.
converted_entities.each do |converted|
@conversion_results.push(UiData::Report::ConversionResult.new(UiData::Report::ConversionStatus::SUCCESS,
obj['id'],
obj['speckle_type'],
converted.persistent_id.to_s,
converted.class))
end
SpeckleEntities::SpeckleEntity.from_speckle_object(state, obj, converted_entities, stream_id)
convert_to_speckle_entities(state, obj, converted_entities)
rescue StandardError => e
puts("Failed to convert #{obj['speckle_type']} (id: #{obj['id']})")
puts(e)
@conversion_results.push(UiData::Report::ConversionResult.new(UiData::Report::ConversionStatus::ERROR,
obj['id'],
obj['speckle_type'],
nil,
nil,
e))
return state, []
return state
end
# rubocop:disable Metrics/CyclomaticComplexity
@@ -372,13 +329,13 @@ module SpeckleConnector3
layer = sketchup_model.layers.find { |l| l.display_name == speckle_object['category'] }
unless layer.nil?
entities.each { |entity| entity.layer = layer if entity.respond_to?(:layer) } if layer
entities.each { |entity| entity.layer = layer } if layer
return state
end
layer = sketchup_model.layers.add(speckle_object['category'])
unless layer.nil?
entities.each { |entity| entity.layer = layer if entity.respond_to?(:layer) } if layer
entities.each { |entity| entity.layer = layer } if layer
state
end
state
@@ -388,27 +345,39 @@ module SpeckleConnector3
# @param state [States::State] state of the speckle application
def create_levels(state, speckle_object)
level = speckle_object['level']
return state if level.nil?
return state unless level['speckle_type'].include?('Objects.BuiltElements.Level')
return state if speckle_object['level'].nil?
return state unless speckle_object['level']['speckle_type'].include?('Objects.BuiltElements.Level')
level_name = level['name'] || level['id']
level_name = speckle_object['level']['name'] || speckle_object['level']['id']
is_exist = @entities_to_fill.grep(Sketchup::SectionPlane).any? { |sp| sp.name == level_name }
return state if is_exist
elevation = SpeckleObjects::Geometry.length_to_native(level['elevation'], level['units'])
elevation = SpeckleObjects::Geometry.length_to_native(speckle_object['level']['elevation'],
speckle_object['level']['units'])
section_plane = @entities_to_fill.add_section_plane([0, 0, elevation + LEVEL_SHIFT_VALUE], [0, 0, -1])
section_plane.name = level_name
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.write_initial_base_data(
section_plane, level['applicationId'], level['id'], level['speckle_type'], [], @stream_id
)
state
end
# @param state [States::State] state of the application
def convert_to_speckle_entities(state, speckle_objects_with_entities)
return state if speckle_objects_with_entities.empty?
def convert_to_speckle_entities(state, speckle_object, entities)
speckle_id = speckle_object['id']
application_id = speckle_object['applicationId']
speckle_type = speckle_object['speckle_type']
children = speckle_object['__closure'].nil? ? [] : speckle_object['__closure']
speckle_state = state.speckle_state
entities.each do |entity|
next if entity.is_a?(Sketchup::Material)
next if (entity.is_a?(Sketchup::Face) || entity.is_a?(Sketchup::Edge)) &&
!state.user_state.user_preferences[:register_speckle_entity]
ent = SpeckleEntities::SpeckleEntity.new(entity, speckle_id, application_id, speckle_type, children,
[stream_id])
ent.write_initial_base_data
speckle_state = speckle_state.with_speckle_entity(ent)
end
state.with_speckle_state(speckle_state)
end
end
# rubocop:enable Metrics/ClassLength
@@ -0,0 +1,230 @@
# frozen_string_literal: true
require_relative 'converter'
require_relative 'base_object_serializer'
require_relative '../speckle_objects/base'
require_relative '../speckle_objects/geometry/line'
require_relative '../speckle_objects/geometry/length'
require_relative '../speckle_objects/geometry/mesh'
require_relative '../speckle_objects/other/block_instance'
require_relative '../speckle_objects/other/block_definition'
require_relative '../speckle_objects/other/rendering_options'
require_relative '../speckle_objects/built_elements/view3d'
require_relative '../constants/path_constants'
module SpeckleConnector
module Converters
# Converts sketchup entities to speckle objects.
class ToSpeckle < Converter
# Convert selected objects by putting them into related array that grouped by layer.
# @return [Hash{Symbol=>Array}] layers -which only have objects- to hold it's objects under the base object.
def convert_selection_to_base(preferences)
layers = add_all_layers
state = speckle_state
sketchup_model.selection.each do |entity|
new_speckle_state, converted_object_with_entity = convert(entity, preferences, state)
state = new_speckle_state
layer_name = entity_layer_path(entity)
layers[layer_name].push(converted_object_with_entity)
end
# send only+ layers that have any object
base_object_properties = layers.reject { |_layer_name, objects| objects.empty? }
add_views(base_object_properties) if sketchup_model.pages.any?
return state, SpeckleObjects::Base.with_detached_layers(base_object_properties)
end
# Add views from pages.
# @param base_object_properties [Hash] dynamically attached base object properties.
def add_views(base_object_properties)
views = []
sketchup_model.pages.each do |page|
cam = page.camera
origin = get_camera_origin(cam)
target = get_camera_target(cam)
direction = get_camera_direction(cam)
update_properties = get_scene_update_properties(page)
rendering_options = SpeckleObjects::Others::RenderingOptions.to_speckle(page.rendering_options)
view = SpeckleObjects::BuiltElements::View3d.new(
page.name, origin, target, direction, SpeckleObjects::Geometry::Vector.new(0, 0, 1, @units),
cam.perspective?, cam.fov, @units, page.name, update_properties, rendering_options
)
views.append(view)
end
base_object_properties['@Named Views'] = views
end
# Get scene properties
# @param page [Sketchup::Page] page on sketchup.
def get_scene_update_properties(page)
{
use_axes: page.use_axes?,
use_camera: page.use_camera?,
use_hidden_geometry: page.use_hidden_geometry?,
use_hidden_layers: page.use_hidden_layers?,
use_hidden_objects: page.use_hidden_objects?,
use_rendering_options: page.use_rendering_options?,
use_section_planes: page.use_section_planes?,
use_shadow_info: page.use_shadow_info?,
use_style: page.use_style?
}
end
# Serialized and traversed information to send batches.
# @param base_and_entity [SpeckleObjects::Base] base object to serialize.
# @return [String, Integer, Array<Object>] base id, total_children_count of base and batches
def serialize(base_and_entity, speckle_state, preferences)
serializer = SpeckleConnector::Converters::BaseObjectSerializer.new(speckle_state, stream_id, preferences)
t = Time.now.to_f
id = serializer.serialize(base_and_entity)
batches = serializer.batch_objects
# write_to_speckle_folder(id, batches)
puts "Generating traversed object elapsed #{Time.now.to_f - t} s"
base_total_children_count = serializer.total_children_count(id)
return id, base_total_children_count, batches, serializer.speckle_state
end
def write_to_speckle_folder(id, batches)
folder_path = "#{HOME_PATH}/Speckle"
file_path = "#{folder_path}/#{id}.json"
FileUtils.mkdir_p(folder_path) unless File.exist?(folder_path)
File.write(file_path, batches.first)
end
# @param entity [Sketchup::Entity] sketchup entity to convert Speckle.
# @param speckle_state [States::SpeckleState] the current speckle state of the {States::State}
# @param parent [Symbol, String] parent of the Sketchup Entity to be converted.
# rubocop:disable Metrics/MethodLength
def convert(entity, preferences, speckle_state, parent = :base)
convert = method(:convert)
if entity.is_a?(Sketchup::Edge)
line = SpeckleObjects::Geometry::Line.from_edge(entity, @units, preferences[:model]).to_h
return speckle_state, [line, [entity]]
end
if entity.is_a?(Sketchup::Face)
mesh = SpeckleObjects::Geometry::Mesh.from_face(entity, @units, preferences[:model])
return speckle_state, [mesh, [entity]]
end
if entity.is_a?(Sketchup::Group)
new_speckle_state, block_instance = SpeckleObjects::Other::BlockInstance.from_group(
entity, @units, preferences, speckle_state, &convert
)
speckle_state = new_speckle_state
return speckle_state, [block_instance, [entity]]
end
if entity.is_a?(Sketchup::ComponentInstance)
new_speckle_state, block_instance = SpeckleObjects::Other::BlockInstance.from_component_instance(
entity, @units, preferences, speckle_state, &convert
)
speckle_state = new_speckle_state
return speckle_state, [block_instance, [entity]]
end
if entity.is_a?(Sketchup::ComponentDefinition)
new_speckle_state, block_definition = SpeckleObjects::Other::BlockDefinition.from_definition(
entity, @units, @definitions, preferences, speckle_state, parent, &convert
)
speckle_state = new_speckle_state
return speckle_state, [block_definition, [entity]]
end
return speckle_state, nil
end
# rubocop:enable Metrics/MethodLength
# Create layers -> {Hash{Symbol=>Array}} from sketchup model with empty array as hash entry values.
# This method add first headless layers (not belong to any folder),
# then goes through each folder, their sub-folders and their layers.
# @return [Hash{Symbol=>Array}] layers from sketchup model with empty array as hash entry values.
def add_all_layers
# add headless layers
layer_objects = add_layers(sketchup_model.layers.layers)
# add layers from folders
add_layers_from_folders(sketchup_model.layers.folders, layer_objects)
layer_objects
end
# @param layers [Array<Sketchup::Layer>] layers in sketchup model
# @return [Hash{Symbol=>Array}] layers with empty array value.
def add_layers(layers, layer_objects = {}, parent_name = '')
layers.each do |layer|
layer_name = parent_name.empty? ? "@#{layer.display_name}" : "#{parent_name}::#{layer.display_name}"
layer_objects[layer_name] = []
end
layer_objects
end
# @param folders [Array<Sketchup::LayerFolder>] layer folders in sketchup model.
# @param layer_objects [Hash{Symbol=>Array}] layer objects to fill in.
# @param parent_name [String] parent folder name to structure layer path before send to Speckle.
# ex: "@#{parent_name}::#{layer_name}"
def add_layers_from_folders(folders, layer_objects, parent_name = '')
folders.each do |folder|
folder_name = parent_name.empty? ? "@#{folder.display_name}" : "#{parent_name}::#{folder.display_name}"
add_layers(folder.layers, layer_objects, folder_name)
add_layers_from_folders(folder.folders, layer_objects, folder_name) unless folder.folders.empty?
end
end
# Find layer path of given Sketchup entity.
# @param entity [Sketchup::Entity] entity to find root layer.
# @return [String] layer path of Sketchup entity.
def entity_layer_path(entity)
layer_name = entity.layer.display_name
if entity.layer.folder.nil?
"@#{layer_name}"
else
folders = folder_name(entity.layer.folder)
path = ''
folders.reverse.each do |folder|
path += "#{folder}::"
end
"@#{path}#{layer_name}"
end
end
# Nested method to retrieve sub-folders until nothing found.
# @return [Array<String>] folder names as list from bottom to top. Might need to be reversed if you want to see
# from top to bottom.
def folder_name(folder, folders = [])
if folder.folder.nil?
folders.push(folder.display_name)
else
folder_name(folder.folder, folders.push(folder.display_name))
end
end
private
def get_camera_direction(cam)
SpeckleObjects::Geometry::Vector.new(
SpeckleObjects::Geometry.length_to_speckle(cam.direction[0], @units),
SpeckleObjects::Geometry.length_to_speckle(cam.direction[1], @units),
SpeckleObjects::Geometry.length_to_speckle(cam.direction[2], @units),
@units
)
end
def get_camera_target(cam)
SpeckleObjects::Geometry::Point.new(
SpeckleObjects::Geometry.length_to_speckle(cam.target[0], @units),
SpeckleObjects::Geometry.length_to_speckle(cam.target[1], @units),
SpeckleObjects::Geometry.length_to_speckle(cam.target[2], @units),
@units
)
end
def get_camera_origin(camera)
SpeckleObjects::Geometry::Point.new(
SpeckleObjects::Geometry.length_to_speckle(camera.eye[0], @units),
SpeckleObjects::Geometry.length_to_speckle(camera.eye[1], @units),
SpeckleObjects::Geometry.length_to_speckle(camera.eye[2], @units),
@units
)
end
end
end
end
@@ -1,6 +1,6 @@
# frozen_string_literal: true
module SpeckleConnector3
module SpeckleConnector
module Converters
SKETCHUP_UNITS = { 0 => 'in', 1 => 'ft', 2 => 'mm', 3 => 'cm', 4 => 'm', 5 => 'yd' }.freeze
SKETCHUP_UNIT_STRINGS = { 'm' => 'm', 'mm' => 'mm', 'ft' => 'feet', 'in' => 'inch', 'yd' => 'yard',
@@ -6,7 +6,7 @@ require_relative 'vector'
# Add json conversion methods
require_relative 'json'
module SpeckleConnector3
module SpeckleConnector
module Immutable
class Hash
# Return a new {Set} containing the keys from this `Hash`.
@@ -2,7 +2,7 @@
require_relative 'list'
module SpeckleConnector3
module SpeckleConnector
module Immutable
# A `Deque` (or double-ended queue) is an ordered, sequential collection of
@@ -288,6 +288,6 @@ module SpeckleConnector3
# one rather than creating many empty deques using `Deque.new`.
#
# @private
EmptyDeque = SpeckleConnector3::Immutable::Deque.empty
EmptyDeque = SpeckleConnector::Immutable::Deque.empty
end
end
@@ -1,4 +1,4 @@
module SpeckleConnector3
module SpeckleConnector
module Immutable
# Helper module for immutable-ruby's sequential collections
@@ -74,7 +74,7 @@ module SpeckleConnector3
# with an added element.
def group_by_with(empty_group, &block)
block ||= lambda { |item| item }
reduce(SpeckleConnector3::Immutable::EmptyHash) do |hash, item|
reduce(SpeckleConnector::Immutable::EmptyHash) do |hash, item|
key = block.call(item)
group = hash.get(key) || empty_group
hash.put(key, group.add(item))
@@ -126,7 +126,7 @@ module SpeckleConnector3
# Convert this collection to a {Set}.
def to_set
SpeckleConnector3::Immutable::Set.new(self)
SpeckleConnector::Immutable::Set.new(self)
end
# Convert this collection to a programmer-readable `String` representation.
@@ -4,7 +4,7 @@ require_relative 'undefined'
require_relative 'enumerable'
require_relative 'trie'
module SpeckleConnector3
module SpeckleConnector
module Immutable
# An `Immutable::Hash` maps a set of unique keys to corresponding values, much
@@ -59,7 +59,7 @@ module Immutable
# hash = Immutable::Hash.new { |missing_key| missing_key * 10 }
# hash[5] # => 50
class Hash
include SpeckleConnector3::Immutable::Enumerable
include SpeckleConnector::Immutable::Enumerable
class << self
# Create a new `Hash` populated with the given key/value pairs.
@@ -916,6 +916,6 @@ module Immutable
# one rather than creating many empty hashes using `Hash.new`.
#
# @private
EmptyHash = SpeckleConnector3::Immutable::Hash.empty
EmptyHash = SpeckleConnector::Immutable::Hash.empty
end
end
@@ -2,7 +2,7 @@
# Define the method to export immutable structures to JSON. Default exporter would only export the name of the class as
# a string. In order to export it properly, we need to add `to_json` methods to the classes we want to serialize as JSON.
module SpeckleConnector3
module SpeckleConnector
module Immutable
class Vector
# Convert the object to JSON
@@ -9,7 +9,7 @@ require_relative 'enumerable'
require_relative 'hash'
require_relative 'set'
module SpeckleConnector3
module SpeckleConnector
module Immutable
class << self
@@ -120,7 +120,7 @@ module Immutable
# traversed to find the element.
#
module List
include SpeckleConnector3::Immutable::Enumerable
include SpeckleConnector::Immutable::Enumerable
# @private
CADR = /^c([ad]+)r$/
@@ -4,7 +4,7 @@ require_relative 'trie'
require_relative 'sorted_set'
require 'set'
module SpeckleConnector3
module SpeckleConnector
module Immutable
# `Immutable::Set` is a collection of unordered values with no duplicates. Testing whether
@@ -47,7 +47,7 @@ module Immutable
# set1.subset?(set3) # => true
#
class Set
include SpeckleConnector3::Immutable::Enumerable
include SpeckleConnector::Immutable::Enumerable
class << self
# Create a new `Set` populated with the given items.
@@ -288,7 +288,7 @@ module Immutable
# @param other [Enumerable] The collection to merge with
# @return [Set]
def union(other)
if other.is_a?(SpeckleConnector3::Immutable::Set)
if other.is_a?(SpeckleConnector::Immutable::Set)
if other.size > size
small_set_pairs = @trie
large_set_trie = other.instance_variable_get(:@trie)
@@ -322,7 +322,7 @@ module Immutable
# @return [Set]
def intersection(other)
if other.size < @trie.size
if other.is_a?(SpeckleConnector3::Immutable::Set)
if other.is_a?(SpeckleConnector::Immutable::Set)
trie = other.instance_variable_get(:@trie).select { |key, _| include?(key) }
else
trie = Trie.new(0)
@@ -344,7 +344,7 @@ module Immutable
# @param other [Enumerable] The collection to subtract from this set
# @return [Set]
def difference(other)
trie = if (@trie.size <= other.size) && (other.is_a?(SpeckleConnector3::Immutable::Set) || (defined?(::Set) && other.is_a?(::Set)))
trie = if (@trie.size <= other.size) && (other.is_a?(SpeckleConnector::Immutable::Set) || (defined?(::Set) && other.is_a?(::Set)))
@trie.select { |key, _| !other.include?(key) }
else
@trie.bulk_delete(other)
@@ -386,7 +386,7 @@ module Immutable
# After doing some benchmarking to estimate the constants, it appears break-even is at ~190 items
# We also check other.size, to avoid the more expensive #is_a? checks in cases where it doesn't matter
#
if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(SpeckleConnector3::Immutable::Set) || other.is_a?(::Set))
if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(SpeckleConnector::Immutable::Set) || other.is_a?(::Set))
other = ::Set.new(other)
end
all? { |item| other.include?(item) }
@@ -417,7 +417,7 @@ module Immutable
def proper_subset?(other)
return false if other.size <= size
# See comments above
if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(SpeckleConnector3::Immutable::Set) || other.is_a?(::Set))
if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(SpeckleConnector::Immutable::Set) || other.is_a?(::Set))
other = ::Set.new(other)
end
all? { |item| other.include?(item) }
@@ -450,7 +450,7 @@ module Immutable
other.each { |item| return false if include?(item) }
else
# See comment on #subset?
if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(SpeckleConnector3::Immutable::Set) || other.is_a?(::Set))
if other.size >= 150 && @trie.size >= 190 && !(other.is_a?(SpeckleConnector::Immutable::Set) || other.is_a?(::Set))
other = ::Set.new(other)
end
each { |item| return false if other.include?(item) }
@@ -584,6 +584,6 @@ module Immutable
# one rather than creating many empty sets using `Set.new`.
#
# @private
EmptySet = SpeckleConnector3::Immutable::Set.empty
EmptySet = SpeckleConnector::Immutable::Set.empty
end
end
@@ -2,7 +2,7 @@
require_relative 'enumerable'
module SpeckleConnector3
module SpeckleConnector
module Immutable
# A `SortedSet` is a collection of ordered values with no duplicates. Unlike a
@@ -52,7 +52,7 @@ module Immutable
# is a constant time operation.
#
class SortedSet
include SpeckleConnector3::Immutable::Enumerable
include SpeckleConnector::Immutable::Enumerable
class << self
# Create a new `SortedSet` populated with the given items. This method does not
@@ -1501,6 +1501,6 @@ module Immutable
# this one rather than creating many empty sorted sets using `SortedSet.new`.
#
# @private
EmptySortedSet = SpeckleConnector3::Immutable::SortedSet.empty
EmptySortedSet = SpeckleConnector::Immutable::SortedSet.empty
end
end

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