Compare commits
261 Commits
2.11.0-rc2
...
2.13.2
| Author | SHA1 | Date | |
|---|---|---|---|
| 5187fded02 | |||
| 370825838a | |||
| bc8eece488 | |||
| 4222b1721d | |||
| b475bc96af | |||
| f9ac7319ae | |||
| 2ce3e9150f | |||
| 8e39832d3e | |||
| ce7ac6da16 | |||
| 57879a57cd | |||
| 6f5b367028 | |||
| 291948c1ef | |||
| 0ca6888fb1 | |||
| 5e92236396 | |||
| 28db40112b | |||
| b2153883ef | |||
| 807cbeb75d | |||
| 4730aebdc6 | |||
| c2279eec87 | |||
| f5567ae9ad | |||
| 2fcba04cf5 | |||
| 0eefd1605f | |||
| 7b88ea022d | |||
| 7a922114ab | |||
| f717c270fd | |||
| ee5ed468c3 | |||
| a9ded445a7 | |||
| 9ecad52df2 | |||
| 8c500985bf | |||
| 3567f9ba5d | |||
| 852bfb716c | |||
| 473956aa3c | |||
| b22fc19400 | |||
| f6a8ece992 | |||
| afc0c21aae | |||
| 08e6106c1f | |||
| 1f9f654e3d | |||
| 501e923760 | |||
| 37b716cfc5 | |||
| 8584db0ab5 | |||
| 5aff3e9692 | |||
| 5d8f03cbc3 | |||
| b6c85ac15a | |||
| c2af757f4e | |||
| 26252c55ac | |||
| 3ce547bcea | |||
| e0492f589e | |||
| 4931288ffd | |||
| 52e0f77a3c | |||
| 20c016f1bf | |||
| 00a271cbba | |||
| 3a6b69634d | |||
| 78cf7f42e3 | |||
| 2b52454c00 | |||
| e032e4561b | |||
| 23ce1e0476 | |||
| 825b7b98d7 | |||
| 3de3128a2a | |||
| 92d70ccc8b | |||
| f8c5154457 | |||
| 3a3874e9f1 | |||
| 61b94c519b | |||
| ac0f46ca66 | |||
| e538cdb509 | |||
| df8a63c5f5 | |||
| 6827a61bf8 | |||
| 20e56c05b3 | |||
| 5494139319 | |||
| c491ba087a | |||
| 5cf045c941 | |||
| 55fea5f077 | |||
| 3a9cb73950 | |||
| d99f613b79 | |||
| 68db8ddff3 | |||
| c7490b264b | |||
| a8e84cfad3 | |||
| 5ec4f7e4e8 | |||
| b019ce2f2e | |||
| dd90e41610 | |||
| bdf78e374d | |||
| 163197f583 | |||
| 8e13bba44a | |||
| e4478b86a5 | |||
| 1b1ae59ed7 | |||
| a54f44d22a | |||
| d2394274eb | |||
| 0eedb0b397 | |||
| 782e9db1f6 | |||
| 2977472c56 | |||
| 8f3a8bfa91 | |||
| 914f6e9516 | |||
| fa6f65cb7c | |||
| dafbe6f995 | |||
| 4a8ac6377b | |||
| c91fac4ec4 | |||
| 00d23b084e | |||
| e0a3b82cea | |||
| e49036d8c4 | |||
| 4a8356692b | |||
| adfb7bc63a | |||
| 3f41cefa88 | |||
| 2074ff1987 | |||
| 65fe189421 | |||
| bb7590eab4 | |||
| 6a04457219 | |||
| 7f8b1d9586 | |||
| 4362cc53bb | |||
| 959e6b2a12 | |||
| 2d91070e5a | |||
| cc038ece32 | |||
| 7af1292f29 | |||
| 2c3fd7a84f | |||
| d36f70dbc7 | |||
| 77f5f29c90 | |||
| ead45ed843 | |||
| a6259bb7eb | |||
| 0249ebeb95 | |||
| 1c3a35a137 | |||
| c12319b417 | |||
| 90ac00a628 | |||
| e5c71cb3a4 | |||
| ebf0681574 | |||
| 642643f412 | |||
| 8dd65ac255 | |||
| 8df58f226b | |||
| 58b4f2ce14 | |||
| 65f0c836fe | |||
| 2aa16085a5 | |||
| 25f05a69c6 | |||
| 5c2a94da16 | |||
| 162325e90e | |||
| b022a8c608 | |||
| 1e404f5e6b | |||
| 8ef7780332 | |||
| 51b4f7b3f7 | |||
| 9b3fa33e50 | |||
| 9349d0813d | |||
| 8d9bc500a1 | |||
| cae7b6e29f | |||
| 0b4ac732b2 | |||
| acacbb91e3 | |||
| fd6d3d9a2f | |||
| 3b531e30b1 | |||
| 0c78085b2e | |||
| 4ab53308f7 | |||
| 06ae161793 | |||
| 94e005d2f8 | |||
| 717072c3a5 | |||
| 02b4bde92a | |||
| a397d1e233 | |||
| d2198c0765 | |||
| 549bf63198 | |||
| 8b824f5342 | |||
| d052d5e8a1 | |||
| 610c22dd02 | |||
| a5496ab6a9 | |||
| c29c8f009c | |||
| fb7e9f2a6c | |||
| 62c2bbb9fa | |||
| 7656772194 | |||
| f22ff050e0 | |||
| 708f0b44fd | |||
| 2ee4581f17 | |||
| e337fb869f | |||
| 4a8b0147e1 | |||
| c95a1c7e1f | |||
| a07cd5c3f5 | |||
| 4d1473582e | |||
| 3bc9f4c452 | |||
| 11377038a0 | |||
| 6053d3eac1 | |||
| 529830f36b | |||
| 505cf6265c | |||
| 8cd9673eec | |||
| ac9cb28558 | |||
| 6eefe0698c | |||
| d0113532b6 | |||
| 5a1d2ad5f4 | |||
| dfe02f4c74 | |||
| 5349d556b9 | |||
| 7f7d8a501b | |||
| cbee0e465b | |||
| faf00f0b0d | |||
| fa97da5781 | |||
| e7bab546db | |||
| e2f4a30b5b | |||
| ccff1df041 | |||
| 2037cfc25a | |||
| 268a091d8a | |||
| 6b52dfab3e | |||
| 839999851f | |||
| a87470b7b5 | |||
| e76aeb80fd | |||
| 28292e59e2 | |||
| 25dda481b2 | |||
| bbda233fd8 | |||
| 349218f0b5 | |||
| f18d00a69d | |||
| 25ea6504de | |||
| 43081c70e2 | |||
| 0fde1c2026 | |||
| b35383571e | |||
| 45a84847a2 | |||
| 70d92f26d6 | |||
| 737ed86e69 | |||
| 3865057b7a | |||
| 42a84dcd86 | |||
| e2d819c59d | |||
| bfee6a88dc | |||
| 68f3be17df | |||
| 929c97ff5e | |||
| 4b66a2e4d0 | |||
| 46e740154e | |||
| 05e89f49da | |||
| 358e9071e3 | |||
| e37b6a1cc0 | |||
| 266721973b | |||
| 7c27ac85cb | |||
| 4b79732e38 | |||
| 2ceeea5298 | |||
| 3ec659a59b | |||
| 4309056851 | |||
| b768f20f7a | |||
| b3a71bcf53 | |||
| 50c199bc03 | |||
| d6302ac128 | |||
| 6a5d9e1394 | |||
| ac5fc3e6ea | |||
| aa6cbceeb9 | |||
| 46a7395382 | |||
| a782811dad | |||
| d22039bc96 | |||
| 15539c258e | |||
| f9ca4acf16 | |||
| 66d2a9b7fe | |||
| 6dff8c3221 | |||
| f13c65e083 | |||
| 56a7d5cb86 | |||
| c63c0675d5 | |||
| 22bc4b8c9e | |||
| ead17b8906 | |||
| 1a211daac2 | |||
| c7e502da4e | |||
| 70df5e6cec | |||
| 793f287c35 | |||
| 8e8b1c60b8 | |||
| ba2cd51852 | |||
| 3986a4ef60 | |||
| c99d89fb11 | |||
| 0a9c33de91 | |||
| 88c940fc53 | |||
| 88c861cbde | |||
| cd071ca144 | |||
| 9970b8ec36 | |||
| ffc564becd | |||
| 6289fd5941 | |||
| 7f44fe76c7 | |||
| ea86dc6785 | |||
| 754f9e1ed1 | |||
| 9a1a02e664 | |||
| 1e92195355 |
+43
-11
@@ -36,19 +36,41 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: ./
|
||||
- run:
|
||||
name: Patch
|
||||
name: Create Innosetup signing cert
|
||||
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 }
|
||||
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 }
|
||||
$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
|
||||
$version = "$($ver).$($env:WORKFLOW_NUM)"
|
||||
python patch_version.py $semver
|
||||
speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss
|
||||
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:
|
||||
@@ -58,9 +80,17 @@ jobs:
|
||||
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 https://$GITHUB_TOKEN@github.com/specklesystems/speckle-sharp-ci-tools.git speckle-sharp-ci-tools
|
||||
command: git clone git@github.com:specklesystems/speckle-sharp-ci-tools.git speckle-sharp-ci-tools
|
||||
- persist_to_workspace:
|
||||
root: ./
|
||||
paths:
|
||||
@@ -114,8 +144,10 @@ workflows:
|
||||
filters:
|
||||
tags:
|
||||
only: /.*/
|
||||
context: innosetup
|
||||
|
||||
- deploy-manager2:
|
||||
context: do-spaces-speckle-releases
|
||||
slug: sketchup
|
||||
os: Win
|
||||
extension: exe
|
||||
|
||||
+1
-1
Submodule _sqlite3 updated: cbf81b6c70...800dd5e366
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative 'deactivate_diffing'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Deactivate diffing for stream.
|
||||
class ActivateDiffing < Action
|
||||
def initialize(stream_id)
|
||||
super()
|
||||
@stream_id = stream_id
|
||||
end
|
||||
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def update_state(state)
|
||||
state = DeactivateDiffing.update_state(state, {})
|
||||
puts "Diffing activated for #{@stream_id}"
|
||||
speckle_entities = state.speckle_state.speckle_entities
|
||||
invalid_speckle_entities = speckle_entities.select do |_id, entity|
|
||||
entity.invalid_stream_ids.include?(@stream_id) && entity.sketchup_entity.is_a?(Sketchup::Face)
|
||||
end
|
||||
invalid_speckle_entities.each do |id, entity|
|
||||
new_entity = entity.activate_diffing(@stream_id, state.sketchup_state.materials.by_id(MAT_EDIT))
|
||||
speckle_entities = speckle_entities.put(id, new_entity)
|
||||
end
|
||||
new_speckle_state = state.speckle_state.with_speckle_entities(speckle_entities)
|
||||
state.with_speckle_state(new_speckle_state)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Adds material to speckle state and Sketchup.
|
||||
class AddMaterial < Action
|
||||
def self.update_state(state, material_name:, color:, material_id:, alpha: nil)
|
||||
materials = state.sketchup_state.materials
|
||||
existing_material = materials.by_id(material_id)
|
||||
return state if existing_material&.valid?
|
||||
|
||||
new_material = create_or_get_material(state.sketchup_state.sketchup_model,
|
||||
material_name,
|
||||
color,
|
||||
material_id,
|
||||
alpha: alpha)
|
||||
new_materials = materials.add_material(material_id, new_material)
|
||||
new_sketchup_state = state.sketchup_state.with(:@materials => new_materials)
|
||||
state.with(:@sketchup_state => new_sketchup_state)
|
||||
end
|
||||
|
||||
def self.create_or_get_material(model, material_name, color, material_id, alpha: nil)
|
||||
materials = model.materials
|
||||
existing_material = materials.find { |mat| mat.name == material_name }
|
||||
return existing_material if existing_material&.valid?
|
||||
|
||||
existing_material = materials.add material_name
|
||||
existing_material.set_attribute(MAT_DICTIONARY, MAT_ID, material_id.to_s)
|
||||
set_hex_color(existing_material, color)
|
||||
existing_material.alpha = alpha if alpha
|
||||
existing_material
|
||||
end
|
||||
|
||||
def self.set_hex_color(skp_material, hex_value)
|
||||
hex_value = hex_value.to_s
|
||||
col_blue, col_green, col_red = parse_hex_color(hex_value)
|
||||
skp_material.color = col_red, col_green, col_blue
|
||||
end
|
||||
|
||||
def self.parse_hex_color(hex_value)
|
||||
split_values = hex_value.match(/^#([a-fA-F\d]{2})([a-fA-F\d]{2})([a-fA-F\d]{2})$/) ||
|
||||
hex_value.match(/^#([a-fA-F\d])([a-fA-F\d])([a-fA-F\d])$/)
|
||||
col_red = split_values[1].hex
|
||||
col_green = split_values[2].hex
|
||||
col_blue = split_values[3].hex
|
||||
return col_blue, col_green, col_red
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative '../ext/sqlite3'
|
||||
require_relative '../constants/path_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Action to collect preferences from database to UI.
|
||||
class CollectPreferences < Action
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def self.update_state(state, _data)
|
||||
state.with_add_queue('collectPreferences', state.user_state.preferences.to_json, [])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Action to collect versions from sketchup and connector to track user's version by mixpanel.
|
||||
class CollectVersions < Action
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def self.update_state(state, _data)
|
||||
versions = {
|
||||
sketchup: Sketchup.version.to_i,
|
||||
speckle: SpeckleConnector::CONNECTOR_VERSION
|
||||
}
|
||||
state.with_add_queue('collectVersions', versions.to_json, [])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Deactivate diffing.
|
||||
class DeactivateDiffing < Action
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def self.update_state(state, _data)
|
||||
puts 'Diffing deactivated!'
|
||||
speckle_entities = state.speckle_state.speckle_entities
|
||||
diffing_activated_speckle_entities = speckle_entities.reject do |_id, entity|
|
||||
entity.active_diffing_stream_id.nil?
|
||||
end
|
||||
diffing_activated_speckle_entities.each do |id, entity|
|
||||
new_entity = entity.deactivate_diffing
|
||||
speckle_entities = speckle_entities.put(id, new_entity)
|
||||
end
|
||||
new_speckle_state = state.speckle_state.with_speckle_entities(speckle_entities)
|
||||
state.with_speckle_state(new_speckle_state)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,56 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_action'
|
||||
require_relative '../load_sketchup_model'
|
||||
require_relative '../collect_preferences'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
module Events
|
||||
# Handle events that are triggered by the {AppObserver}.
|
||||
class AppEventAction < EventAction
|
||||
# Handle loading new or existing model
|
||||
class OnNewOrChangedModel
|
||||
# Handle events when the new or existing model is loaded in Sketchup
|
||||
# @param state [States::State] the current state of speckle application
|
||||
# @param event_data [Array<(Sketchup::Model)>] the event data for the given event. It consists of
|
||||
# a double array with a single element that is the {Sketchup::Model} object of the loaded model.
|
||||
def self.update_state(state, event_data)
|
||||
return state unless event_data&.any?
|
||||
|
||||
model = event_data.flatten.first
|
||||
# LoadSketchupModel action should be responsible to update all model specific data for state and then
|
||||
# should notify the UI to update it's components.
|
||||
new_state = Actions::LoadSketchupModel.update_state(state, model)
|
||||
# Action to let UI to render itself with new preferences state
|
||||
# TODO: Later UI should be updated if any stream is invalid after
|
||||
# we collected speckle_entities appropriately
|
||||
CollectPreferences.update_state(new_state, {})
|
||||
end
|
||||
end
|
||||
|
||||
# Run actions that are needed before the sketchup quits
|
||||
class OnQuit
|
||||
# Handle when Sketchup application closes
|
||||
# @param state [States::State] the current state of speckle application
|
||||
# @param _event_data [Array] the event data
|
||||
# @return [States::State] the transformed state object
|
||||
def self.update_state(state, _event_data)
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
# Handlers that are used to handle specific events
|
||||
ACTIONS = {
|
||||
onNewModel: OnNewOrChangedModel,
|
||||
onOpenModel: OnNewOrChangedModel,
|
||||
onQuit: OnQuit
|
||||
}.freeze
|
||||
|
||||
def self.actions
|
||||
ACTIONS
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -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
|
||||
@@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# This module contains actions that are performed to handle events triggered by observers in Sketchup.
|
||||
module Events
|
||||
# Base action for Handling events
|
||||
class EventAction
|
||||
def self.actions
|
||||
raise NoMethodError, 'Implement in a subclass'
|
||||
end
|
||||
|
||||
# Handle the events that were collected by the observer. In case of the selection observer,
|
||||
# we only need to handle the events once if any of the events actually happened.
|
||||
# @param state [States::State] the current state of the SpeckleConnector Application
|
||||
# @param events [Hash{Symbol=>Array}] the event data grouped by the event name
|
||||
# @return [States::State] the transformed state
|
||||
def self.update_state(state, events)
|
||||
# Don't do anything if there are no events for this action
|
||||
return state unless events
|
||||
|
||||
actions = self.actions
|
||||
actions.each do |event_name, action|
|
||||
next unless events.key?(event_name)
|
||||
|
||||
event_data = events[event_name]
|
||||
state = action.update_state(state, event_data)
|
||||
end
|
||||
state
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_action'
|
||||
require_relative '../load_sketchup_model'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
module Events
|
||||
# Handle events that are triggered by the {ModelObserver}.
|
||||
class ModelEventAction < EventAction
|
||||
# Handle loading new or existing model
|
||||
class OnActivePathChanged
|
||||
# Handle events when the new or existing model is loaded in Sketchup
|
||||
# @param state [States::State] the current state of speckle application
|
||||
# @param event_data [Array<(Sketchup::Model)>] the event data for the given event. It consists of
|
||||
# a double array with a single element that is the {Sketchup::Model} object of the loaded model.
|
||||
def self.update_state(state, _event_data)
|
||||
sketchup_state = state.sketchup_state
|
||||
active_path = sketchup_state.sketchup_model.active_path
|
||||
observers = state.speckle_state.observers
|
||||
update_object_observers(active_path, observers)
|
||||
return state
|
||||
end
|
||||
|
||||
def self.update_object_observers(path, observers)
|
||||
path[-1].definition.entities.add_observer(observers[ENTITIES_OBSERVER]) unless path.nil?
|
||||
end
|
||||
end
|
||||
|
||||
# Handlers that are used to handle specific events
|
||||
ACTIONS = {
|
||||
onActivePathChanged: OnActivePathChanged
|
||||
}.freeze
|
||||
|
||||
def self.actions
|
||||
ACTIONS
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -12,8 +12,8 @@ module SpeckleConnector
|
||||
# @return [States::State] the new updated state object
|
||||
def self.update_state(state, _data)
|
||||
puts 'Initialisation of Speckle accounts requested by plugin'
|
||||
accounts_data = Accounts.load_accounts.to_json
|
||||
state.with_add_queue('loadAccounts', accounts_data, [])
|
||||
accounts_data = state.speckle_state.accounts
|
||||
state.with_add_queue('loadAccounts', accounts_data.to_json, [])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative 'add_material'
|
||||
require_relative '../constants/mat_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Action to initialize materials
|
||||
class InitializeMaterials < Action
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def self.update_state(state)
|
||||
new_state = recreate_material(state, DEFAULT_NAMES[MAT_ADD], DEFAULT_COLORS[MAT_ADD], MAT_ADD)
|
||||
new_state = recreate_material(new_state, DEFAULT_NAMES[MAT_EDIT], DEFAULT_COLORS[MAT_EDIT], MAT_EDIT)
|
||||
recreate_material(new_state, DEFAULT_NAMES[MAT_REMOVE], DEFAULT_COLORS[MAT_REMOVE], MAT_REMOVE)
|
||||
end
|
||||
|
||||
def self.recreate_material(state, name, color, id, alpha: nil)
|
||||
Actions::AddMaterial.update_state(
|
||||
state,
|
||||
material_name: name,
|
||||
color: color,
|
||||
material_id: id,
|
||||
alpha: alpha
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,6 +5,8 @@ require_relative '../states/state'
|
||||
require_relative '../states/speckle_state'
|
||||
require_relative '../states/sketchup_state'
|
||||
require_relative '../accounts/accounts'
|
||||
require_relative '../preferences/preferences'
|
||||
require_relative '../constants/observer_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
@@ -12,12 +14,20 @@ module SpeckleConnector
|
||||
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)
|
||||
accounts = SpeckleConnector::Accounts.load_accounts.to_json
|
||||
speckle_state = States::SpeckleState.new(accounts, {}, {})
|
||||
def self.update_state(state, observers)
|
||||
attach_app_observer!(observers[APP_OBSERVER])
|
||||
accounts = SpeckleConnector::Accounts.load_accounts
|
||||
speckle_state = States::SpeckleState.new(accounts, observers, {}, {})
|
||||
# This should be the only point that `Sketchup_active_model` passed to application state.
|
||||
sketchup_state = States::SketchupState.new(Sketchup.active_model)
|
||||
States::State.new(state.user_state, speckle_state, sketchup_state, false)
|
||||
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)
|
||||
Actions::LoadSketchupModel.update_state(state, sketchup_state.sketchup_model)
|
||||
end
|
||||
|
||||
def self.attach_app_observer!(observer)
|
||||
Sketchup.add_observer(observer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -9,7 +9,8 @@ module SpeckleConnector
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def self.update_state(state, _data)
|
||||
(saved_streams = state.sketchup_state.sketchup_model.attribute_dictionary('speckle', true)['streams']) or []
|
||||
(saved_streams = state.sketchup_state.sketchup_model
|
||||
.attribute_dictionary('Speckle', true)['saved_streams']) or []
|
||||
state.with_add_queue('setSavedStreams', saved_streams, [])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative 'initialize_materials'
|
||||
require_relative '../sketchup_model/reader/speckle_entities_reader'
|
||||
require_relative '../preferences/preferences'
|
||||
require_relative '../states/state'
|
||||
require_relative '../states/sketchup_state'
|
||||
require_relative '../constants/observer_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Switch sketchup model wit a new one
|
||||
class LoadSketchupModel < Action
|
||||
# Replace current model state with the state of a new model. This action is triggered when user opens new or
|
||||
# existing Sketchup model.
|
||||
# @param state [States::State] the current state of Speckle
|
||||
# @param additional_parameters [Array] parameters that the action takes
|
||||
# @return [States::State] the new updated state object
|
||||
def self.update_state(state, sketchup_model)
|
||||
# Init sketchup state again with new model
|
||||
new_sketchup_state = States::SketchupState.new(sketchup_model)
|
||||
sketchup_model.rendering_options['DisplaySectionPlanes'] = true
|
||||
new_state = state.with(:@sketchup_state => new_sketchup_state)
|
||||
# Init materials again
|
||||
new_state = InitializeMaterials.update_state(new_state)
|
||||
|
||||
# Read speckle entities
|
||||
new_speckle_entities = SketchupModel::Reader::SpeckleEntitiesReader.read(sketchup_model.entities)
|
||||
new_speckle_state = new_state.speckle_state.with_speckle_entities(Immutable::Hash.new(new_speckle_entities))
|
||||
new_state = new_state.with_speckle_state(new_speckle_state)
|
||||
|
||||
# Read preferences from database and model.
|
||||
preferences = Preferences.read_preferences(new_state.sketchup_state.sketchup_model)
|
||||
new_user_state = new_state.user_state.with_preferences(preferences)
|
||||
new_state = new_state.with(:@user_state => new_user_state)
|
||||
attach_observers(sketchup_model, new_state.speckle_state.observers)
|
||||
new_state
|
||||
end
|
||||
|
||||
# Attach observers to the sketchup model
|
||||
# @param sketchup_model [Sketchup::Model] the model to attach observers to
|
||||
# @param observers [Hash{Class=>}] the observer objects indexed by their class that will be attached
|
||||
def self.attach_observers(sketchup_model, observers)
|
||||
# selection = sketchup_model.selection
|
||||
# selection.add_observer(observers[SELECTION_OBSERVER_NAME])
|
||||
# layers = sketchup_model.layers
|
||||
# layers.add_observer(observers[LAYERS_OBSERVER_NAME])
|
||||
entities = sketchup_model.entities
|
||||
entities.add_observer(observers[ENTITIES_OBSERVER])
|
||||
sketchup_model.add_observer(observers[MODEL_OBSERVER])
|
||||
# materials = sketchup_model.materials
|
||||
# materials.add_observer(observers[MATERIALS_OBSERVER_NAME])
|
||||
# pages = sketchup_model.pages
|
||||
# pages.add_observer(observers[PAGES_OBSERVER_NAME])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative '../ext/sqlite3'
|
||||
require_relative '../accounts/accounts'
|
||||
require_relative '../constants/path_constants'
|
||||
require_relative '../sketchup_model/dictionary/speckle_model_dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# When preference updated by UI.
|
||||
class ModelPreferencesUpdated < Action
|
||||
def initialize(pref, value)
|
||||
super()
|
||||
@preference = pref
|
||||
@value = value
|
||||
end
|
||||
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def update_state(state)
|
||||
model = state.user_state.preferences[:model].dup
|
||||
model[@preference.to_sym] = @value
|
||||
new_preferences = state.user_state.preferences.put(:model, model)
|
||||
SketchupModel::Dictionary::SpeckleModelDictionaryHandler.set_attribute(
|
||||
state.sketchup_state.sketchup_model,
|
||||
@preference.to_sym,
|
||||
@value,
|
||||
'Speckle'
|
||||
)
|
||||
new_user_state = state.user_state.with_preferences(new_preferences)
|
||||
state.with_user_state(new_user_state)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative 'events/app_event_action'
|
||||
require_relative 'events/entities_event_action'
|
||||
require_relative 'events/model_event_action'
|
||||
require_relative '../constants/observer_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Handle events that were collected by observers
|
||||
class OnEventsAction < Action
|
||||
RUN_ORDER = {
|
||||
APP_OBSERVER => Events::AppEventAction,
|
||||
ENTITIES_OBSERVER => Events::EntitiesEventAction,
|
||||
MODEL_OBSERVER => Events::ModelEventAction
|
||||
# MATERIALS_OBSERVER => Events::MaterialsEventAction,
|
||||
# LAYERS_OBSERVER => Events::LayerEventAction,
|
||||
# PAGES_OBSERVER => Events::PagesEventAction,
|
||||
# SELECTION_OBSERVER => Events::SelectionEventAction
|
||||
}.freeze
|
||||
|
||||
def self.update_state(state, events)
|
||||
RUN_ORDER.each do |observer_name, action|
|
||||
next unless events.key?(observer_name)
|
||||
|
||||
parameters = events[observer_name]
|
||||
state = action.update_state(state, parameters)
|
||||
end
|
||||
state
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -8,24 +8,28 @@ module SpeckleConnector
|
||||
module Actions
|
||||
# Action to receive objects from Speckle Server.
|
||||
class ReceiveObjects < Action
|
||||
def initialize(stream_id, base, stream_name, branch_name, branch_id)
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def initialize(stream_id, base, stream_name, branch_name, branch_id, source_app)
|
||||
super()
|
||||
@stream_id = stream_id
|
||||
@base = base
|
||||
@stream_name = stream_name
|
||||
@branch_name = branch_name
|
||||
@branch_id = branch_id
|
||||
@source_app = source_app
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def update_state(state)
|
||||
converter = Converters::ToNative.new(state.sketchup_state.sketchup_model)
|
||||
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
|
||||
converter.receive_commit_object(@base)
|
||||
state = converter.receive_commit_object(@base)
|
||||
elapsed_time = (Time.now.to_f - start_time).round(3)
|
||||
puts "==== Converting to Native executed in #{elapsed_time} sec ===="
|
||||
puts "==== Source application is #{@source_app}. ===="
|
||||
state.with_add_queue('finishedReceiveInSketchup', @stream_id, [])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,8 +12,10 @@ module SpeckleConnector
|
||||
# @return [States::State] the new updated state object
|
||||
def self.update_state(state, _data)
|
||||
puts 'Reload of Speckle accounts requested by plugin'
|
||||
accounts_data = Accounts.load_accounts.to_json
|
||||
state.with_add_queue('loadAccounts', accounts_data, [])
|
||||
new_speckle_state = state.speckle_state.with_accounts(Accounts.load_accounts)
|
||||
state = state.with_speckle_state(new_speckle_state)
|
||||
accounts_data = state.speckle_state.accounts
|
||||
state.with_add_queue('loadAccounts', accounts_data.to_json, [])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,10 +18,10 @@ module SpeckleConnector
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def update_state(state)
|
||||
speckle_dict = state.sketchup_state.sketchup_model.attribute_dictionary('speckle', true)
|
||||
saved = speckle_dict['streams'] || []
|
||||
speckle_dict = state.sketchup_state.sketchup_model.attribute_dictionary('Speckle', true)
|
||||
saved = speckle_dict['saved_streams'] || []
|
||||
saved -= [@stream_id]
|
||||
speckle_dict['streams'] = saved
|
||||
speckle_dict['saved_streams'] = saved
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,10 +16,10 @@ module SpeckleConnector
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def update_state(state)
|
||||
speckle_dict = state.sketchup_state.sketchup_model.attribute_dictionary('speckle', true)
|
||||
saved = speckle_dict['streams'] || []
|
||||
speckle_dict = state.sketchup_state.sketchup_model.attribute_dictionary('Speckle', true)
|
||||
saved = speckle_dict['saved_streams'] || []
|
||||
saved = saved.empty? ? [@stream_id] : saved.unshift(@stream_id)
|
||||
speckle_dict['streams'] = saved
|
||||
speckle_dict['saved_streams'] = saved
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,11 +16,10 @@ module SpeckleConnector
|
||||
# @return [States::State] the new updated state object
|
||||
def update_state(state)
|
||||
to_send_stream_id = state.speckle_state.stream_queue[:stream_id]
|
||||
return state if to_send_stream_id == @stream_id
|
||||
return state if to_send_stream_id == @stream_id || to_send_stream_id.nil?
|
||||
|
||||
to_send_converted = state.speckle_state.stream_queue[:converted].to_json
|
||||
new_state = state.with_add_queue('convertedFromSketchup', to_send_stream_id, [to_send_converted])
|
||||
new_state = new_state.with_add_queue('oneClickSend', to_send_stream_id, [])
|
||||
new_state.with_empty_stream_queue
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative 'deactivate_diffing'
|
||||
require_relative '../convertors/units'
|
||||
require_relative '../convertors/to_speckle'
|
||||
|
||||
@@ -16,16 +17,23 @@ module SpeckleConnector
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def update_state(state)
|
||||
sketchup_model = state.sketchup_state.sketchup_model
|
||||
converter = Converters::ToSpeckle.new(sketchup_model)
|
||||
base = converter.convert_selection_to_base
|
||||
id, total_children_count, batches = converter.send_info(base)
|
||||
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}")
|
||||
state.with_add_queue('convertedFromSketchup', @stream_id, [
|
||||
{ is_string: false, val: batches },
|
||||
{ is_string: true, val: id },
|
||||
{ is_string: false, val: total_children_count }
|
||||
])
|
||||
|
||||
# This is the place we can send information to UI for diffing check
|
||||
diffing = state.user_state.preferences[:user][:diffing]
|
||||
new_speckle_state = new_speckle_state.with_invalid_streams_queue if diffing
|
||||
|
||||
new_state = state.with_speckle_state(new_speckle_state)
|
||||
new_state.with_add_queue('convertedFromSketchup', @stream_id, [
|
||||
{ is_string: false, val: batches },
|
||||
{ is_string: true, val: id },
|
||||
{ is_string: false, val: total_children_count }
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative '../ext/sqlite3'
|
||||
require_relative '../accounts/accounts'
|
||||
require_relative '../constants/path_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# When preference updated by UI.
|
||||
class UserPreferencesUpdated < Action
|
||||
def initialize(pref_hash, pref, value)
|
||||
super()
|
||||
@preference_hash = pref_hash
|
||||
@preference = pref
|
||||
@value = value
|
||||
end
|
||||
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def update_state(state)
|
||||
# Init sqlite database
|
||||
db = Sqlite3::Database.new(SPECKLE_CONFIG_DB_PATH)
|
||||
|
||||
# Select data
|
||||
data = db.exec("SELECT content FROM 'objects' WHERE hash = '#{@preference_hash}'").first.first
|
||||
|
||||
# Parse string to hash
|
||||
data_hash = JSON.parse(data).to_h
|
||||
|
||||
# Get current preference value
|
||||
old_preference_value = data_hash[@preference]
|
||||
|
||||
# Return old state if it is equal to new one
|
||||
return state if @value == old_preference_value
|
||||
|
||||
data_hash[@preference] = @value
|
||||
|
||||
# Update entry unless equal old to new
|
||||
db.exec("UPDATE 'objects' SET content = '#{data_hash.to_json}' WHERE hash = '#{@preference_hash}'")
|
||||
|
||||
# Close db when process done
|
||||
db.close
|
||||
|
||||
user = state.user_state.preferences[:user].dup
|
||||
user[@preference.to_sym] = @value
|
||||
new_preferences = state.user_state.preferences.put(:user, user)
|
||||
new_user_state = state.user_state.with_preferences(new_preferences)
|
||||
# This is the place we can send information to UI for diffing check. It is a technical depth!
|
||||
if @preference == 'diffing'
|
||||
new_speckle_state = if @value
|
||||
state.speckle_state.with_invalid_streams_queue
|
||||
else
|
||||
state.speckle_state.with_empty_invalid_streams_queue
|
||||
end
|
||||
state = state.with_speckle_state(new_speckle_state)
|
||||
end
|
||||
state.with_user_state(new_user_state)
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -15,6 +15,9 @@ module SpeckleConnector
|
||||
# @return [Ui::UiController] controller for ui views
|
||||
attr_reader :ui_controller
|
||||
|
||||
# @return [Observers::Handler] the observers indexed by their classes to handle
|
||||
attr_reader :observer_handler
|
||||
|
||||
def initialize(menu_commands, state, ui_controller)
|
||||
@menu_commands = menu_commands
|
||||
@state = state
|
||||
@@ -35,6 +38,10 @@ module SpeckleConnector
|
||||
update_state!(Actions::ClearQueue)
|
||||
end
|
||||
|
||||
def add_observer_handler!(observer_handler)
|
||||
@observer_handler = observer_handler
|
||||
end
|
||||
|
||||
def update_state!(action, *parameters)
|
||||
old_state = @state
|
||||
@state = action.update_state(old_state, *parameters)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'command'
|
||||
require_relative '../actions/activate_diffing'
|
||||
|
||||
module SpeckleConnector
|
||||
module Commands
|
||||
# Command to activate diffing for stream.
|
||||
class ActivateDiffing < Command
|
||||
def _run(data)
|
||||
stream_id = data['stream_id']
|
||||
action = Actions::ActivateDiffing.new(stream_id)
|
||||
app.update_state!(action)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -18,11 +18,22 @@ module SpeckleConnector
|
||||
|
||||
def run(*parameters)
|
||||
# Run here common operations that same for each command.
|
||||
_run(*parameters)
|
||||
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
|
||||
|
||||
@@ -4,6 +4,7 @@ require_relative 'command'
|
||||
require_relative '../states/initial_state'
|
||||
require_relative '../ui/vue_view'
|
||||
require_relative '../actions/initialize_speckle'
|
||||
require_relative '../observers/factory'
|
||||
|
||||
module SpeckleConnector
|
||||
module Commands
|
||||
@@ -30,7 +31,10 @@ module SpeckleConnector
|
||||
# Do the actual Speckle initialization.
|
||||
def initialize_speckle(app)
|
||||
# TODO: Initialize here speckle states and observers.
|
||||
app.update_state!(Actions::InitializeSpeckle)
|
||||
observer_handler = Observers::Factory.create_handler(app)
|
||||
app.add_observer_handler!(observer_handler)
|
||||
observers = Observers::Factory.create_observers(observer_handler)
|
||||
app.update_state!(Actions::InitializeSpeckle, observers)
|
||||
dialog_specs = {
|
||||
dialog_id: Ui::SPECKLE_UI_ID,
|
||||
htm_file: Ui::VUE_UI_HTML,
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'command'
|
||||
require_relative '../accounts/accounts'
|
||||
require_relative '../actions/model_preference_updated'
|
||||
|
||||
module SpeckleConnector
|
||||
module Commands
|
||||
# Command to update theme.
|
||||
class ModelPreferencesUpdated < Command
|
||||
def _run(data)
|
||||
preference = data['preference']
|
||||
new_value = data['value']
|
||||
app.update_state!(Actions::ModelPreferencesUpdated.new(preference, new_value))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -13,7 +13,8 @@ module SpeckleConnector
|
||||
branch_name = data['branch_name']
|
||||
branch_id = data['branch_id']
|
||||
stream_name = data['stream_name']
|
||||
action = Actions::ReceiveObjects.new(stream_id, base, stream_name, branch_name, branch_id)
|
||||
source_app = data['source_app']
|
||||
action = Actions::ReceiveObjects.new(stream_id, base, stream_name, branch_name, branch_id, source_app)
|
||||
app.update_state!(action)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'command'
|
||||
require_relative '../accounts/accounts'
|
||||
require_relative '../actions/user_preferences_updated'
|
||||
|
||||
module SpeckleConnector
|
||||
module Commands
|
||||
# Command to update preferences.
|
||||
class UserPreferencesUpdated < Command
|
||||
def _run(data)
|
||||
preference_hash = data['preference_hash']
|
||||
preference = data['preference']
|
||||
new_value = data['value']
|
||||
app.update_state!(Actions::UserPreferencesUpdated.new(preference_hash, preference, new_value))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,8 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SpeckleConnector
|
||||
SPECKLE_BASE_OBJECT = 'Speckle_Base_Object'
|
||||
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'
|
||||
INVALID_STREAM_IDS = 'invalid_stream_ids'
|
||||
end
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SpeckleConnector
|
||||
MAT_DICTIONARY = 'Speckle_Connector_Materials'
|
||||
MAT_ID = 'Speckle_Connector_Material_Id'
|
||||
|
||||
MAT_ADD = :speckle_connector_add_material
|
||||
MAT_EDIT = :speckle_connector_edit_material
|
||||
MAT_REMOVE = :speckle_connector_remove_material
|
||||
|
||||
DEFAULT_COLORS = {
|
||||
MAT_ADD => '#66FF66',
|
||||
MAT_EDIT => '#FFFF9F',
|
||||
MAT_REMOVE => '#FF6666'
|
||||
}.freeze
|
||||
|
||||
DEFAULT_NAMES = {
|
||||
MAT_ADD => 'Speckle_Material_Add',
|
||||
MAT_EDIT => 'Speckle_Material_Edit',
|
||||
MAT_REMOVE => 'Speckle_Material_Remove'
|
||||
}.freeze
|
||||
end
|
||||
@@ -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
|
||||
@@ -7,6 +7,7 @@ require_relative 'platform_constants'
|
||||
module SpeckleConnector
|
||||
dir = __dir__.dup
|
||||
dir.force_encoding('UTF-8') if dir.respond_to?(:force_encoding)
|
||||
HOME_PATH = (ENV['HOME']).to_s
|
||||
SPECKLE_SRC_PATH = Pathname.new(File.expand_path('..', dir)).cleanpath.to_s
|
||||
SPECKLE_APPDATA_PATH = case OPERATING_SYSTEM
|
||||
when OS_WIN
|
||||
@@ -18,4 +19,6 @@ module SpeckleConnector
|
||||
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, 'Config.db')
|
||||
SPECKLE_TEST_DB_PATH = File.join(SPECKLE_APPDATA_PATH, 'sketchup_test.db')
|
||||
end
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../speckle_objects/geometry/length'
|
||||
|
||||
module SpeckleConnector
|
||||
COMBINE_FACES_BY_MATERIAL = :combine_faces_by_material
|
||||
INCLUDE_ENTITY_ATTRIBUTES = :include_entity_attributes
|
||||
INCLUDE_FACE_ENTITY_ATTRIBUTES = :include_face_entity_attributes
|
||||
INCLUDE_EDGE_ENTITY_ATTRIBUTES = :include_edge_entity_attributes
|
||||
INCLUDE_GROUP_ENTITY_ATTRIBUTES = :include_group_entity_attributes
|
||||
INCLUDE_COMPONENT_ENTITY_ATTRIBUTES = :include_component_entity_attributes
|
||||
MERGE_COPLANAR_FACES = :merge_coplanar_faces
|
||||
|
||||
LEVEL_SHIFT_VALUE = SpeckleObjects::Geometry.length_to_native(1.5, 'm')
|
||||
|
||||
DEFAULT_MODEL_PREFERENCES = {
|
||||
COMBINE_FACES_BY_MATERIAL => true,
|
||||
INCLUDE_ENTITY_ATTRIBUTES => true,
|
||||
INCLUDE_FACE_ENTITY_ATTRIBUTES => true,
|
||||
INCLUDE_EDGE_ENTITY_ATTRIBUTES => true,
|
||||
INCLUDE_GROUP_ENTITY_ATTRIBUTES => true,
|
||||
INCLUDE_COMPONENT_ENTITY_ATTRIBUTES => true,
|
||||
MERGE_COPLANAR_FACES => true
|
||||
}.freeze
|
||||
end
|
||||
@@ -2,4 +2,18 @@
|
||||
|
||||
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,6 +5,7 @@ require 'securerandom'
|
||||
# rubocop:enable SketchupPerformance/OpenSSL
|
||||
require 'digest'
|
||||
require_relative 'converter'
|
||||
require_relative '../speckle_entities/speckle_entity'
|
||||
require_relative '../relations/many_to_one_relation'
|
||||
|
||||
module SpeckleConnector
|
||||
@@ -15,7 +16,16 @@ module SpeckleConnector
|
||||
# @return [Integer] default chunk size the determine splitting base prop into chucks
|
||||
attr_reader :default_chunk_size
|
||||
|
||||
def initialize(default_chunk_size = 1000)
|
||||
# @return [String] stream id to send conversion
|
||||
attr_reader :stream_id
|
||||
|
||||
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 = []
|
||||
@@ -30,31 +40,55 @@ module SpeckleConnector
|
||||
def serialize(base)
|
||||
id, traversed = traverse_base(base)
|
||||
@objects[id] = traversed
|
||||
return id, traversed
|
||||
id
|
||||
end
|
||||
|
||||
def total_children_count(id)
|
||||
@objects[id][:totalChildrenCount]
|
||||
end
|
||||
|
||||
# @param base [Object] base object to populate all children and their relationship
|
||||
# @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. Initialize traversed base object that will be filled with traversed values or
|
||||
# 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: '')
|
||||
|
||||
# 3.1 Remove applicationId if it is nil
|
||||
traversed_base.delete(:applicationId)
|
||||
|
||||
# 3. Iterate all entries (key, value) of the base {Base > Hash} object
|
||||
# 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`
|
||||
|
||||
# 4. Get last item from detach_lineage array
|
||||
is_detached = @detach_lineage.pop
|
||||
|
||||
# 5. Add closures
|
||||
closure = {}
|
||||
parent = @lineage.pop
|
||||
@@ -82,9 +116,19 @@ module SpeckleConnector
|
||||
# 10. Save object string if detached
|
||||
@objects[id] = traversed_base if is_detached
|
||||
|
||||
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
|
||||
@@ -102,14 +146,16 @@ module SpeckleConnector
|
||||
next
|
||||
end
|
||||
|
||||
# 3.3. Determine prop is detached or not
|
||||
is_prop_detach = prop[0] == '@'
|
||||
# 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
|
||||
|
||||
@@ -144,7 +190,7 @@ module SpeckleConnector
|
||||
chunk_references = []
|
||||
|
||||
chunks.each do |chunk_element|
|
||||
@detach_lineage.append(is_prop_detach)
|
||||
@detach_lineage.append(is_detach_prop)
|
||||
id, _traversed = traverse_base(chunk_element)
|
||||
chunk_references.append(detach_helper(id))
|
||||
end
|
||||
@@ -152,17 +198,21 @@ module SpeckleConnector
|
||||
# 3.5.7. Add chunk references to the traversed base prop without @(<chunk_size>)
|
||||
traversed_base[prop.to_s.sub(chunked_detach_match[0], '')] = chunk_references
|
||||
|
||||
# 3.5.8. We are done chunking, good to go next
|
||||
# 3.5.8. We are done chunking, good to go next property
|
||||
next
|
||||
end
|
||||
|
||||
child = traverse_value(value, is_detach_prop)
|
||||
|
||||
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.6. traverse value according to value is a speckle object or not
|
||||
if value.is_a?(Hash) && !value[:speckle_type].nil?
|
||||
child = traverse_value(value, is_prop_detach)
|
||||
traversed_base[prop] = is_prop_detach ? detach_helper(child[:id]) : child
|
||||
else
|
||||
traversed_base[prop] = traverse_value(value, is_prop_detach)
|
||||
end
|
||||
traversed_base[prop] = if is_base
|
||||
is_detach_prop ? detach_helper(child[:id]) : child
|
||||
else
|
||||
child
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
@@ -171,38 +221,57 @@ module SpeckleConnector
|
||||
# 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
|
||||
# rubocop:disable Style/OptionalBooleanParameter
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def traverse_value(value, is_detach = false)
|
||||
# 1. Return same value if value is primitive type (string, numeric, boolean)
|
||||
return value unless value.is_a?(Hash) || value.is_a?(Array)
|
||||
|
||||
# 2. 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
|
||||
return value.collect { |el| traverse_value(el) } unless is_detach
|
||||
unless is_detach
|
||||
values = value.collect do |el|
|
||||
el_value = traverse_value(el)
|
||||
el_value
|
||||
end
|
||||
return values
|
||||
end
|
||||
|
||||
# 2.2. If it is detached than collect them into detached_list
|
||||
detached_list = []
|
||||
value.each do |el|
|
||||
if (el.is_a?(Array) || 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))
|
||||
else
|
||||
detached_list.append(traverse_value(el, is_detach))
|
||||
el_value = traverse_value(el, is_detach)
|
||||
detached_list.append(el_value)
|
||||
end
|
||||
end
|
||||
return detached_list
|
||||
end
|
||||
|
||||
# 3. Hash
|
||||
return value if value[:speckle_type].nil?
|
||||
return value if value.is_a?(Hash) && value[:speckle_type].nil?
|
||||
|
||||
# 4. Base objects
|
||||
unless value[:speckle_type].nil?
|
||||
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
|
||||
@@ -215,6 +284,7 @@ module SpeckleConnector
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
# rubocop:enable Metrics/PerceivedComplexity
|
||||
# rubocop:enable Style/OptionalBooleanParameter
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
def detach_helper(reference_id)
|
||||
@lineage.each do |parent|
|
||||
@@ -263,6 +333,32 @@ module SpeckleConnector
|
||||
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
|
||||
|
||||
@@ -17,6 +17,11 @@ module SpeckleConnector
|
||||
faces.each { |face| face.edges.each { |edge| edges << edge } }
|
||||
edges.uniq!
|
||||
edges.each { |edge| remove_edge_have_coplanar_faces(edge, faces, false) }
|
||||
merged_faces(faces)
|
||||
end
|
||||
|
||||
def self.merged_faces(faces)
|
||||
faces.reject(&:deleted?)
|
||||
end
|
||||
|
||||
# Detect edges to remove by checking following controls respectively;
|
||||
|
||||
@@ -4,18 +4,32 @@ module SpeckleConnector
|
||||
module Converters
|
||||
# Helper class to convert geometries between server and Sketchup.
|
||||
class Converter
|
||||
# @return [States::State] the current state of the {SpeckleConnector::App}
|
||||
attr_reader :state
|
||||
|
||||
# @return [States::SpeckleState] the current speckle state of the {States::State}
|
||||
attr_reader :speckle_state
|
||||
|
||||
# @return [Sketchup::Model] active sketchup model.
|
||||
attr_reader :sketchup_model
|
||||
|
||||
attr_accessor :units, :definitions, :registry, :entity_observer
|
||||
# @return [String] stream id that conversion happening with it
|
||||
attr_reader :stream_id
|
||||
|
||||
def initialize(sketchup_model)
|
||||
@sketchup_model = sketchup_model
|
||||
su_unit = @sketchup_model.options['UnitsOptions']['LengthUnit']
|
||||
# @return [String] speckle units
|
||||
attr_reader :units
|
||||
|
||||
attr_accessor :definitions
|
||||
|
||||
# @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]
|
||||
@definitions = {}
|
||||
# @registry = Sketchup.active_model.attribute_dictionary("speckle_id_registry", true)
|
||||
# @entity_observer = SpeckleEntityObserver.new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,18 +1,36 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'converter'
|
||||
require_relative '../constants/type_constants'
|
||||
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/other/revit/revit_instance'
|
||||
require_relative '../speckle_objects/geometry/point'
|
||||
require_relative '../speckle_objects/geometry/line'
|
||||
require_relative '../speckle_objects/geometry/mesh'
|
||||
require_relative '../speckle_objects/built_elements/view3d'
|
||||
|
||||
module SpeckleConnector
|
||||
module Converters
|
||||
# Converts sketchup entities to speckle objects.
|
||||
# rubocop:disable Metrics/ClassLength
|
||||
class ToNative < Converter
|
||||
# @return [States::SpeckleState] the current speckle state of the {States::State}
|
||||
attr_accessor :speckle_state
|
||||
|
||||
# @return [String] source application of received object that will be converted to native
|
||||
attr_reader :source_app
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
# Module aliases
|
||||
GEOMETRY = SpeckleObjects::Geometry
|
||||
OTHER = SpeckleObjects::Other
|
||||
@@ -23,6 +41,9 @@ module SpeckleConnector
|
||||
MESH = GEOMETRY::Mesh
|
||||
BLOCK_DEFINITION = OTHER::BlockDefinition
|
||||
BLOCK_INSTANCE = OTHER::BlockInstance
|
||||
REVIT_INSTANCE = OTHER::Revit::RevitInstance
|
||||
RENDER_MATERIAL = OTHER::RenderMaterial
|
||||
DISPLAY_VALUE = OTHER::DisplayValue
|
||||
|
||||
BASE_OBJECT_PROPS = %w[applicationId id speckle_type totalChildrenCount].freeze
|
||||
CONVERTABLE_SPECKLE_TYPES = %w[
|
||||
@@ -31,11 +52,114 @@ module SpeckleConnector
|
||||
Objects.Geometry.Mesh
|
||||
Objects.Geometry.Brep
|
||||
Objects.Other.BlockInstance
|
||||
Objects.Other.Revit.RevitInstance
|
||||
Objects.Other.BlockDefinition
|
||||
Objects.Other.RenderMaterial
|
||||
Objects.Other.Instance:Objects.Other.BlockInstance
|
||||
].freeze
|
||||
|
||||
def can_convert_to_native(obj)
|
||||
def from_revit
|
||||
@from_revit ||= source_app.include?('revit')
|
||||
end
|
||||
|
||||
def from_sketchup
|
||||
@from_sketchup ||= source_app.include?('sketchup')
|
||||
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)
|
||||
# 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' }
|
||||
@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
|
||||
@state
|
||||
end
|
||||
|
||||
def levels_layer
|
||||
@levels_layer ||= sketchup_model.layers.add('Levels')
|
||||
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
|
||||
return unless from_revit
|
||||
|
||||
section_planes = @entities_to_fill.grep(Sketchup::SectionPlane)
|
||||
bbox = @entities_to_fill.parent.bounds
|
||||
c_1 = bbox.corner(0)
|
||||
c_2 = bbox.corner(1)
|
||||
c_3 = bbox.corner(3)
|
||||
c_4 = bbox.corner(2)
|
||||
section_planes.each do |section_plane|
|
||||
level_name = "#{@definition_name}-#{section_plane.name}"
|
||||
definition = sketchup_model.definitions.add(level_name)
|
||||
@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)
|
||||
c3_e = Geom::Point3d.new(c_3.x, c_3.y, elevation - LEVEL_SHIFT_VALUE)
|
||||
c4_e = Geom::Point3d.new(c_4.x, c_4.y, elevation - LEVEL_SHIFT_VALUE)
|
||||
cline_1 = definition.entities.add_cline(c1_e, c2_e)
|
||||
cline_2 = definition.entities.add_cline(c2_e, c3_e)
|
||||
cline_3 = definition.entities.add_cline(c3_e, c4_e)
|
||||
cline_4 = definition.entities.add_cline(c4_e, c1_e)
|
||||
text = definition.entities.add_text(" #{section_plane.name}", c1_e)
|
||||
[cline_1, cline_2, cline_3, cline_4, text, definition].each { |o| o.layer = levels_layer }
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
def entities_to_fill(_obj)
|
||||
return sketchup_model.entities if from_sketchup
|
||||
|
||||
@definition_name = "#{@branch_name}-#{@stream_name}"
|
||||
definition = sketchup_model.definitions.find { |d| d.name == @definition_name }
|
||||
if definition.nil?
|
||||
definition = sketchup_model.definitions.add(@definition_name)
|
||||
sketchup_model.entities.add_instance(definition, Geom::Transformation.new)
|
||||
end
|
||||
definition.entities
|
||||
end
|
||||
|
||||
LAYERS_WILL_BE_HIDDEN = [
|
||||
'Rooms',
|
||||
'Mass',
|
||||
'Mass Floor',
|
||||
'Grid',
|
||||
'Shaft Openings'
|
||||
].freeze
|
||||
|
||||
def check_hiding_layers_needed
|
||||
return unless from_revit
|
||||
|
||||
sketchup_model.layers.each do |layer|
|
||||
if LAYERS_WILL_BE_HIDDEN.any? { |layer_name| layer.display_name.include?(layer_name) }
|
||||
layer.visible = false
|
||||
sketchup_model.pages.each { |page| page.update(PAGE_USE_LAYER_VISIBILITY) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Conditions for converting speckle object to native sketchup entity:
|
||||
# 1- `obj` is a hash
|
||||
# 2- `obj` has a property as 'speckle_type'
|
||||
# 3- `obj` is a convertable 'speckle_type' which sketchup supports
|
||||
# @param obj [Object] candidate object to convert from speckle to sketchup.
|
||||
# @return [Boolean] whether object is convertable or not.
|
||||
def convertible_to_native?(obj)
|
||||
return false unless obj.is_a?(Hash) && obj.key?('speckle_type')
|
||||
|
||||
CONVERTABLE_SPECKLE_TYPES.include?(obj['speckle_type'])
|
||||
@@ -45,15 +169,6 @@ module SpeckleConnector
|
||||
['Objects.BuiltElements.Revit.Parameter'].include?(obj['speckle_type'])
|
||||
end
|
||||
|
||||
# @param obj [Object] speckle commit object.
|
||||
def receive_commit_object(obj)
|
||||
# First create layers on the sketchup before starting traversing
|
||||
create_layers(obj.keys.filter_map { |key| key if key.start_with?('@') }, sketchup_model.layers)
|
||||
# Define default commit layer which is the fallback
|
||||
default_commit_layer = sketchup_model.layers.layers.find { |layer| layer.display_name == '@Untagged' }
|
||||
traverse_commit_object(obj, sketchup_model.layers, default_commit_layer)
|
||||
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.
|
||||
@@ -107,9 +222,9 @@ module SpeckleConnector
|
||||
# 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, commit_folder, layer)
|
||||
if can_convert_to_native(obj)
|
||||
convert_to_native(obj, layer)
|
||||
def traverse_commit_object(obj, commit_folder, layer, entities)
|
||||
if convertible_to_native?(obj)
|
||||
@state = convert_to_native(@state, obj, layer, entities)
|
||||
elsif obj.is_a?(Hash) && obj.key?('speckle_type')
|
||||
return if ignored_speckle_type?(obj)
|
||||
|
||||
@@ -119,16 +234,16 @@ module SpeckleConnector
|
||||
props.each do |prop|
|
||||
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)
|
||||
traverse_commit_object(obj[prop], commit_folder, layer, entities)
|
||||
end
|
||||
else
|
||||
# puts(">>> Found #{obj['speckle_type']}: #{obj['id']} with displayValue.")
|
||||
convert_to_native(obj, layer)
|
||||
@state = convert_to_native(@state, obj, layer, entities)
|
||||
end
|
||||
elsif obj.is_a?(Hash)
|
||||
obj.each_value { |value| traverse_commit_object(value, commit_folder, layer) }
|
||||
obj.each_value { |value| traverse_commit_object(value, commit_folder, layer, entities) }
|
||||
elsif obj.is_a?(Array)
|
||||
obj.each { |value| traverse_commit_object(value, commit_folder, layer) }
|
||||
obj.each { |value| traverse_commit_object(value, commit_folder, layer, entities) }
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
@@ -166,53 +281,105 @@ module SpeckleConnector
|
||||
end
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
def convert_to_native(obj, layer, entities = sketchup_model.entities)
|
||||
convert = method(:convert_to_native)
|
||||
return display_value_to_native_component(obj, layer, entities, &convert) unless obj['displayValue'].nil?
|
||||
def speckle_object_to_native(obj)
|
||||
return DISPLAY_VALUE.method(:to_native) unless obj['displayValue'].nil?
|
||||
|
||||
case obj['speckle_type']
|
||||
when 'Objects.Geometry.Line', 'Objects.Geometry.Polyline' then LINE.to_native(obj, layer, entities)
|
||||
when 'Objects.Other.BlockInstance' then BLOCK_INSTANCE.to_native(sketchup_model, obj, layer, entities, &convert)
|
||||
when 'Objects.Other.BlockDefinition' then BLOCK_DEFINITION.to_native(sketchup_model, obj, layer,
|
||||
obj['name'],
|
||||
entities,
|
||||
&convert)
|
||||
when 'Objects.Geometry.Mesh' then MESH.to_native(sketchup_model, obj, layer, entities)
|
||||
when 'Objects.Geometry.Brep' then MESH.to_native(sketchup_model, obj['displayValue'], layer, entities)
|
||||
SPECKLE_OBJECT_TO_NATIVE[obj['speckle_type']]
|
||||
end
|
||||
|
||||
SPECKLE_OBJECT_TO_NATIVE = {
|
||||
OBJECTS_GEOMETRY_LINE => LINE.method(:to_native),
|
||||
OBJECTS_GEOMETRY_POLYLINE => LINE.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)
|
||||
}.freeze
|
||||
|
||||
# @param state [States::State] state of the speckle application
|
||||
def convert_to_native(state, obj, layer, entities = sketchup_model.entities)
|
||||
# store this method as parameter to re-call it inner callstack
|
||||
convert_to_native = method(:convert_to_native)
|
||||
# Get 'to_native' method to convert upcoming speckle object to native sketchup entity
|
||||
to_native_method = speckle_object_to_native(obj)
|
||||
# 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)
|
||||
if from_revit
|
||||
# Create levels as section planes if they exists
|
||||
create_levels(state, obj)
|
||||
# Create layers from category of object and place object in it
|
||||
create_layers_from_categories(state, obj, converted_entities)
|
||||
end
|
||||
# Create speckle entities from sketchup entities to achieve continuous traversal.
|
||||
convert_to_speckle_entities(state, obj, converted_entities)
|
||||
rescue StandardError => e
|
||||
puts("Failed to convert #{obj['speckle_type']} (id: #{obj['id']})")
|
||||
puts(e)
|
||||
nil
|
||||
return state
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
# rubocop:disable Metrics/PerceivedComplexity
|
||||
def create_layers_from_categories(state, speckle_object, entities)
|
||||
return state if speckle_object['category'].nil?
|
||||
|
||||
layer = sketchup_model.layers.find { |l| l.display_name == speckle_object['category'] }
|
||||
unless layer.nil?
|
||||
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 layer
|
||||
state
|
||||
end
|
||||
state
|
||||
end
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
# rubocop:enable Metrics/PerceivedComplexity
|
||||
|
||||
# Creates a component definition and instance from a speckle object with a display value
|
||||
def display_value_to_native_component(obj, layer, entities, &convert)
|
||||
obj_id = obj['applicationId'].to_s.empty? ? obj['id'] : obj['applicationId']
|
||||
definition = BLOCK_DEFINITION.to_native(
|
||||
sketchup_model,
|
||||
obj['displayValue'],
|
||||
layer,
|
||||
"def::#{obj_id}",
|
||||
obj_id,
|
||||
&convert
|
||||
)
|
||||
# @param state [States::State] state of the speckle application
|
||||
def create_levels(state, speckle_object)
|
||||
return state if speckle_object['level'].nil?
|
||||
return state unless speckle_object['level']['speckle_type'].include?('Objects.BuiltElements.Level')
|
||||
|
||||
find_and_erase_existing_instance(definition, obj_id)
|
||||
t_arr = obj['transform']
|
||||
transform = t_arr.nil? ? Geom::Transformation.new : OTHER::Transform.to_native(t_arr, units)
|
||||
instance = entities.add_instance(definition, transform)
|
||||
instance.name = obj_id
|
||||
instance
|
||||
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(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
|
||||
state
|
||||
end
|
||||
|
||||
# Takes a component definition and finds and erases the first instance with the matching name
|
||||
# (and optionally the applicationId)
|
||||
def find_and_erase_existing_instance(definition, name, app_id = '')
|
||||
definition.instances.find { |ins| ins.name == name || ins.guid == app_id }&.erase!
|
||||
# @param state [States::State] state of the application
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
@@ -4,64 +4,136 @@ 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
|
||||
# @return [Hash{Symbol=>Array}] layers to hold it's objects under the base object.
|
||||
attr_reader :layers
|
||||
|
||||
def initialize(sketchup_model)
|
||||
super(sketchup_model)
|
||||
@layers = add_all_layers
|
||||
end
|
||||
|
||||
# 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
|
||||
def convert_selection_to_base(preferences)
|
||||
layers = add_all_layers
|
||||
state = speckle_state
|
||||
sketchup_model.selection.each do |entity|
|
||||
converted_object = convert(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)
|
||||
layers[layer_name].push(converted_object_with_entity)
|
||||
end
|
||||
# send only layers that have any object
|
||||
# send only+ layers that have any object
|
||||
base_object_properties = layers.reject { |_layer_name, objects| objects.empty? }
|
||||
SpeckleObjects::Base.with_detached_layers(base_object_properties)
|
||||
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 [SpeckleObjects::Base] base object to serialize.
|
||||
# @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 send_info(base)
|
||||
serializer = SpeckleConnector::Converters::BaseObjectSerializer.new
|
||||
# t = Time.now.to_f
|
||||
id, _traversed = serializer.serialize(base)
|
||||
# puts "Generating traversed object elapsed #{Time.now.to_f - t} s"
|
||||
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, serializer.batch_objects
|
||||
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.
|
||||
def convert(entity)
|
||||
# @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)
|
||||
return SpeckleObjects::Geometry::Line.from_edge(entity, @units).to_h if entity.is_a?(Sketchup::Edge)
|
||||
return SpeckleObjects::Geometry::Mesh.from_face(entity, @units) if entity.is_a?(Sketchup::Face)
|
||||
if entity.is_a?(Sketchup::Group)
|
||||
return SpeckleObjects::Other::BlockInstance.from_group(entity, @units, @definitions, &convert)
|
||||
end
|
||||
if entity.is_a?(Sketchup::ComponentInstance)
|
||||
return SpeckleObjects::Other::BlockInstance.from_component_instance(entity, @units, @definitions, &convert)
|
||||
end
|
||||
if entity.is_a?(Sketchup::ComponentDefinition)
|
||||
return SpeckleObjects::Other::BlockDefinition.from_definition(entity, @units, @definitions, &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
|
||||
|
||||
nil
|
||||
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),
|
||||
@@ -124,6 +196,35 @@ module SpeckleConnector
|
||||
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
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_observer'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# App observer.
|
||||
class AppObserver < Sketchup::AppObserver
|
||||
include EventObserver
|
||||
|
||||
# rubocop:disable Naming/MethodName
|
||||
# SketchUp observer method triggered when new empty model is created.
|
||||
#
|
||||
# @param model (Sketchup::Model): The active model object.
|
||||
def onNewModel(model)
|
||||
push_event(:onNewModel, model)
|
||||
end
|
||||
|
||||
# SketchUp observer method triggered when previously saved model is opened.
|
||||
#
|
||||
# @param model (Sketchup::Model) - The active model object.
|
||||
def onOpenModel(model)
|
||||
push_event(:onOpenModel, model)
|
||||
end
|
||||
|
||||
# SketchUp observer method triggered when user exists SketchUp.
|
||||
def onQuit
|
||||
push_event(:onQuit)
|
||||
end
|
||||
# rubocop:enable Naming/MethodName
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,26 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_observer'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# Entities observer.
|
||||
class EntitiesObserver < Sketchup::EntitiesObserver
|
||||
attr_accessor :registry
|
||||
|
||||
def initialize
|
||||
super()
|
||||
@registry = Sketchup.active_model.attribute_dictionary('speckle_id_registry', true)
|
||||
end
|
||||
include EventObserver
|
||||
|
||||
# rubocop:disable Naming/MethodName
|
||||
def onEraseEntity(entity)
|
||||
app_id = entity.get_attribute('speckle', 'applicationId')
|
||||
return if app_id.nil?
|
||||
# @param entities (Sketchup::Entities)
|
||||
# @param entity (Sketchup::Entity)
|
||||
def onElementAdded(entities, entity)
|
||||
push_event(:onElementAdded, entities, entity)
|
||||
end
|
||||
|
||||
p(app_id)
|
||||
# @param entities (Sketchup::Entities)
|
||||
# @param entity (Sketchup::Entity)
|
||||
def onElementModified(entities, entity)
|
||||
push_event(:onElementModified, entities, entity)
|
||||
end
|
||||
|
||||
@registry.delete_key(app_id)
|
||||
|
||||
p(@registry)
|
||||
# @param entities (Sketchup::Entities)
|
||||
# @param entity_id (Integer) id of the removed entity.
|
||||
def onElementRemoved(entities, entity_id)
|
||||
push_event(:onElementRemoved, entities, entity_id)
|
||||
end
|
||||
# rubocop:enable Naming/MethodName
|
||||
end
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_hash'
|
||||
require_relative '../actions/on_events_action'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# Event handler class.
|
||||
class EventHandler
|
||||
# @return [SpeckleConnectorApp] an object that contains current state of speckle objects
|
||||
attr_reader :app
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def handle_events(events)
|
||||
run_handlers(events)
|
||||
end
|
||||
|
||||
def run_handlers(events)
|
||||
app.update_state!(Actions::OnEventsAction, events)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../ext/immutable_ruby/core'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# Collection of events.
|
||||
class EventHash < Immutable::Hash
|
||||
def push_event(observer_class, event_name, data)
|
||||
observer_events = self[observer_class] || Immutable::EmptyHash
|
||||
name_events = observer_events[event_name] || Immutable::EmptyVector
|
||||
name_events = name_events.add(data)
|
||||
observer_events = observer_events.put(event_name, name_events)
|
||||
put(observer_class, observer_events)
|
||||
end
|
||||
end
|
||||
|
||||
EMPTY_EVENT_HASH = EventHash.new([])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_hash'
|
||||
require_relative '../actions/on_events_action'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# Observer classes includes it to check common operations for all observer classes.
|
||||
module EventObserver
|
||||
# @return [ObserverHandler] handler for observer events
|
||||
attr_reader :observer_handler
|
||||
|
||||
def initialize(observer_handler)
|
||||
super()
|
||||
@observer_handler = observer_handler
|
||||
end
|
||||
|
||||
def push_event(event_name, *event_data)
|
||||
return if observers_disabled?
|
||||
|
||||
@observer_handler.handle_event!(self.class.name, event_name, event_data)
|
||||
end
|
||||
|
||||
def observers_disabled?
|
||||
@observer_handler.observers_disabled?
|
||||
end
|
||||
|
||||
# Push event only once. If the event is already registered, don't push it again
|
||||
# @param event_name [Symbol] the name of the event
|
||||
# @param event_data [Array] the optional data that comes with the event
|
||||
def push_once(event_name, *event_data)
|
||||
return if observers_disabled?
|
||||
|
||||
# Don't push anything if the selection event was already registered
|
||||
class_events = @observer_handler.events[self.class]
|
||||
events = class_events && class_events[event_name]
|
||||
return if events&.any?
|
||||
|
||||
push_event(event_name, *event_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'app_observer'
|
||||
require_relative 'entities_observer'
|
||||
require_relative 'observer_handler'
|
||||
require_relative 'model_observer'
|
||||
require_relative 'event_handler'
|
||||
require_relative '../constants/observer_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# Factory class to create observers and it's handler
|
||||
module Factory
|
||||
module_function
|
||||
|
||||
def create_handler(app)
|
||||
event_handler = EventHandler.new(app)
|
||||
ObserverHandler.new(event_handler)
|
||||
end
|
||||
|
||||
def create_observers(handler)
|
||||
{
|
||||
APP_OBSERVER => AppObserver.new(handler),
|
||||
ENTITIES_OBSERVER => EntitiesObserver.new(handler),
|
||||
MODEL_OBSERVER => ModelObserver.new(handler)
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_observer'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# Model related event observers.
|
||||
class ModelObserver < Sketchup::ModelObserver
|
||||
include EventObserver
|
||||
|
||||
# rubocop:disable Naming/MethodName
|
||||
# @param model (Sketchup::Model)
|
||||
def onActivePathChanged(model)
|
||||
push_event(:onActivePathChanged, model)
|
||||
end
|
||||
# rubocop:enable Naming/MethodName
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,68 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_hash'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# Class to handle observer events.
|
||||
class ObserverHandler
|
||||
# @return [EventHash] registered events
|
||||
attr_reader :events
|
||||
|
||||
# @return [Float] time when first observer was added
|
||||
attr_reader :start_time
|
||||
|
||||
def initialize(event_handler)
|
||||
@event_handler = event_handler
|
||||
clear_events!
|
||||
@observers_disabled = false
|
||||
@observers_in_progress = false
|
||||
@timer_in_progress = false
|
||||
end
|
||||
|
||||
def handle_event!(observer_class, event_name, data)
|
||||
puts "## Register #{observer_class} event #{event_name} ##"
|
||||
puts " data = #{data.inspect}"
|
||||
return if observers_disabled?
|
||||
|
||||
@events = @events.push_event(observer_class, event_name, data)
|
||||
finish
|
||||
end
|
||||
|
||||
def finish
|
||||
return if @observers_in_progress
|
||||
|
||||
@observers_in_progress = true
|
||||
@start_time = Time.now.to_f
|
||||
UI.start_timer(0, false) { finish_in_timer }
|
||||
end
|
||||
|
||||
def finish_in_timer
|
||||
return if @timer_in_progress
|
||||
|
||||
@timer_in_progress = true
|
||||
@event_handler.handle_events(events)
|
||||
ensure
|
||||
clear_events!
|
||||
@observers_in_progress = false
|
||||
@timer_in_progress = false
|
||||
end
|
||||
|
||||
def observers_disabled?
|
||||
@observers_disabled
|
||||
end
|
||||
|
||||
def with_observers_disabled(&block)
|
||||
previous_state = @observers_disabled
|
||||
@observers_disabled = true
|
||||
block.call
|
||||
ensure
|
||||
@observers_disabled = previous_state
|
||||
end
|
||||
|
||||
def clear_events!
|
||||
@events = EMPTY_EVENT_HASH
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,98 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../ext/sqlite3'
|
||||
require_relative '../immutable/immutable'
|
||||
require_relative '../constants/path_constants'
|
||||
require_relative '../constants/pref_constants'
|
||||
require_relative '../sketchup_model/dictionary/speckle_model_dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
# Preferences that stored on config database and sketchup_model.
|
||||
module Preferences
|
||||
include Immutable::ImmutableUtils
|
||||
DICT_HANDLER = SketchupModel::Dictionary::SpeckleModelDictionaryHandler
|
||||
# rubocop:disable Layout/LineLength
|
||||
DEFAULT_CONFIG = "('configSketchup', '{\"dark_theme\":false, \"diffing\":false, \"register_speckle_entity\":false}');"
|
||||
# rubocop:enable Layout/LineLength
|
||||
DEFAULT_PREFERENCES = '{"dark_theme":false, "diffing":false, "register_speckle_entity": false}'
|
||||
|
||||
# @param sketchup_model [Sketchup::Model] active model.
|
||||
def self.read_preferences(sketchup_model)
|
||||
db = Sqlite3::Database.new(SPECKLE_CONFIG_DB_PATH)
|
||||
user_preferences = validate_user_preferences(db)
|
||||
model_preferences = validate_model_preferences(sketchup_model)
|
||||
Immutable::Hash.new(
|
||||
{
|
||||
user: user_preferences,
|
||||
model: model_preferences
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
# Whether row data is complete with preference or not.
|
||||
# It is useful for backward compatibility, when we add new preferences it should be reset when user initialize it.
|
||||
def self.data_complete?(row_data)
|
||||
return false if row_data.empty?
|
||||
|
||||
data = JSON.parse(row_data.first.first)
|
||||
return false if data['dark_theme'].nil? || data['diffing'].nil? || data['register_speckle_entity'].nil?
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# Validates current preferences. If there are incomplete data then this method resets it with default preferences.
|
||||
# @param database [Sqlite3::Database] database for queries.
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def self.validate_user_preferences(database)
|
||||
row_data = database.exec("SELECT content FROM 'objects' WHERE hash = 'configSketchup'")
|
||||
is_config_sketchup_exist = !row_data.empty?
|
||||
is_data_complete = data_complete?(row_data)
|
||||
if !is_config_sketchup_exist || !is_data_complete
|
||||
if is_config_sketchup_exist
|
||||
unless is_data_complete
|
||||
# Update table with default preferences
|
||||
database.exec("UPDATE 'objects' SET content = '#{DEFAULT_PREFERENCES}' WHERE hash = 'configSketchup'")
|
||||
end
|
||||
else
|
||||
# Insert configSketchup completely to objects.
|
||||
database.exec("INSERT INTO 'objects' VALUES #{DEFAULT_CONFIG}")
|
||||
end
|
||||
end
|
||||
|
||||
# Select data
|
||||
row_data = database.exec("SELECT content FROM 'objects' WHERE hash = 'configSketchup'").first.first
|
||||
# Parse string to hash
|
||||
data_hash = JSON.parse(row_data).to_h
|
||||
# Get current theme value
|
||||
dark_theme = data_hash['dark_theme']
|
||||
diffing = data_hash['diffing']
|
||||
register_speckle_entity = data_hash['register_speckle_entity']
|
||||
|
||||
{
|
||||
dark_theme: dark_theme,
|
||||
diffing: diffing,
|
||||
register_speckle_entity: register_speckle_entity
|
||||
}.freeze
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
# @param sketchup_model [Sketchup::Model] sketchup model to validate model preferences
|
||||
def self.validate_model_preferences(sketchup_model)
|
||||
speckle_dictionary = sketchup_model.attribute_dictionary('Speckle')
|
||||
if speckle_dictionary.nil?
|
||||
DICT_HANDLER.write_initial_model_data(sketchup_model, DEFAULT_MODEL_PREFERENCES)
|
||||
return DEFAULT_MODEL_PREFERENCES
|
||||
end
|
||||
|
||||
DEFAULT_MODEL_PREFERENCES.collect do |pref_key, default_value|
|
||||
pref_value = DICT_HANDLER.get_attribute(
|
||||
sketchup_model,
|
||||
pref_key,
|
||||
'Speckle'
|
||||
)
|
||||
DICT_HANDLER.set_attribute(sketchup_model, pref_key, default_value, 'Speckle') if pref_value.nil?
|
||||
pref_value.nil? ? [pref_key, default_value] : [pref_key, pref_value]
|
||||
end.to_h
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -8,6 +8,8 @@ module SpeckleConnector
|
||||
# `A has child B` is Many to One relationship. Each person can have only one parent,
|
||||
# but one parent can have several children.
|
||||
class ManyToOneRelation
|
||||
attr_reader :parent_table, :children_table
|
||||
|
||||
# @param child [Object] the child element in the relation to look for parent
|
||||
# @return [Object] the parent element that is in relation with the child
|
||||
# @return [nil] if the child element is not in the relation with any elements
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../constants/dict_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
# Operations related to {SketchupModel}.
|
||||
module SketchupModel
|
||||
# Definitions to store for entities.
|
||||
class Definitions
|
||||
def self.from_sketchup_model(model)
|
||||
definitions = model.definitions
|
||||
definitions = definitions.select do |definition|
|
||||
!definition.attribute_dictionaries.nil? &&
|
||||
definition.attribute_dictionaries.any? { |dict| dict.name == SPECKLE_BASE_OBJECT }
|
||||
end
|
||||
Definitions.new(definitions)
|
||||
end
|
||||
|
||||
def add_definition(definition)
|
||||
old_definition = @definitions_by_guid[definition.guid]
|
||||
return self if definition == old_definition
|
||||
|
||||
new_definitions = @definitions.append(definition)
|
||||
Definitions.new(new_definitions)
|
||||
end
|
||||
|
||||
def initialize(definitions = [])
|
||||
@definitions = definitions
|
||||
@definitions_by_name = definitions.collect do |definition|
|
||||
[definition.name, definition]
|
||||
end.to_h.freeze
|
||||
@definitions_by_guid = definitions.collect do |definition|
|
||||
[definition.guid, definition]
|
||||
end.to_h.freeze
|
||||
freeze
|
||||
end
|
||||
|
||||
def by_guid(guid)
|
||||
@definitions_by_guid[guid]
|
||||
end
|
||||
|
||||
def by_name(name)
|
||||
@definitions_by_name[name]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -9,6 +9,57 @@ module SpeckleConnector
|
||||
class DictionaryHandler
|
||||
DICTIONARY_NAME = 'Speckle_Base_Object'
|
||||
|
||||
IGNORED_DICTIONARY_NAMES = [
|
||||
DICTIONARY_NAME,
|
||||
'IFC 4',
|
||||
'IFC 2x3'
|
||||
].freeze
|
||||
|
||||
# @param entity [Sketchup::Entity] entity to get attribute dictionaries
|
||||
def self.attribute_dictionaries_to_speckle(entity)
|
||||
dictionaries = {}
|
||||
return dictionaries if entity.attribute_dictionaries.nil?
|
||||
|
||||
entity.attribute_dictionaries.each do |att_dict|
|
||||
dict_name = att_dict == '' ? 'empty_dictionary_name' : att_dict.name
|
||||
dictionaries[dict_name] = att_dict.to_h unless IGNORED_DICTIONARY_NAMES.include?(att_dict.name)
|
||||
end
|
||||
dictionaries
|
||||
end
|
||||
|
||||
# @param entity [Sketchup::Entity] entity to set attribute dictionaries
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
def self.attribute_dictionaries_to_native(entity, dictionaries)
|
||||
return if dictionaries.nil?
|
||||
|
||||
classification_to_native(entity, dictionaries) if entity.is_a?(Sketchup::ComponentDefinition)
|
||||
|
||||
dictionaries.each do |dict_name, entries|
|
||||
next unless entries.is_a?(Hash)
|
||||
|
||||
dict_name = dict_name == 'empty_dictionary_name' ? '' : dict_name
|
||||
entries.each do |key, value|
|
||||
set_attribute(entity, key, value, dict_name)
|
||||
rescue StandardError => e
|
||||
puts("Failed to write key: #{key} value: #{value} to dictionary #{dict_name}")
|
||||
puts(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
|
||||
# Classification is ComponentDefinition specific, so they can be added only definition by add_classification
|
||||
# method.
|
||||
# @param definition_entity [Sketchup::ComponentDefinition] definition to add callback
|
||||
def self.classification_to_native(definition_entity, dictionaries)
|
||||
applied_schema_types = dictionaries['AppliedSchemaTypes']
|
||||
return if applied_schema_types.nil?
|
||||
|
||||
applied_schema_types.each do |key, value|
|
||||
definition_entity.add_classification(key, value)
|
||||
end
|
||||
end
|
||||
|
||||
# @param entity [Sketchup::Entity] the sketchup entity of Speckle object
|
||||
# @param key [Symbol] the name of the attribute
|
||||
# @param dictionary_name [String, Symbol] the name of the attribute dictionary
|
||||
|
||||
+10
-5
@@ -11,16 +11,21 @@ module SpeckleConnector
|
||||
class SpeckleEntityDictionaryHandler < DictionaryHandler
|
||||
# Writes initial data while speckle entity is creating first time.
|
||||
# @param sketchup_entity [Sketchup::Entity] Sketchup entity to write data into it's attribute dictionary.
|
||||
def self.write_initial_base_data(sketchup_entity)
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def self.write_initial_base_data(sketchup_entity, application_id, id, speckle_type, children, stream_id)
|
||||
initial_dict_data = {
|
||||
# Add here more if you want to write here initial data
|
||||
SPECKLE_ID => '',
|
||||
SPECKLE_TYPE => BASE_OBJECT,
|
||||
APPLICATION_ID => '',
|
||||
TOTAL_CHILDREN_COUNT => 0
|
||||
SPECKLE_ID => id,
|
||||
APPLICATION_ID => application_id,
|
||||
SPECKLE_TYPE => speckle_type,
|
||||
TOTAL_CHILDREN_COUNT => children.length,
|
||||
CHILDREN => children,
|
||||
VALID_STREAM_IDS => [stream_id],
|
||||
INVALID_STREAM_IDS => []
|
||||
}
|
||||
set_hash(sketchup_entity, initial_dict_data)
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'dictionary_handler'
|
||||
require_relative '../../constants/dict_constants'
|
||||
require_relative '../../constants/type_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module SketchupModel
|
||||
module Dictionary
|
||||
# Dictionary handler of the speckle model.
|
||||
class SpeckleModelDictionaryHandler < DictionaryHandler
|
||||
DICTIONARY_NAME = 'Speckle'
|
||||
# Writes initial data while speckle entity is creating first time.
|
||||
# @param sketchup_model [Sketchup::Model] Sketchup model to write data into it's attribute dictionary.
|
||||
def self.write_initial_model_data(sketchup_model, default_preferences)
|
||||
set_hash(sketchup_model, default_preferences, DICTIONARY_NAME)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../constants/mat_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
# Operations related to {SketchupModel}.
|
||||
module SketchupModel
|
||||
# Materials to store for entities.
|
||||
class Materials
|
||||
def self.from_sketchup_model(model)
|
||||
materials = model.materials
|
||||
materials = materials.select do |material|
|
||||
!material.attribute_dictionaries.nil? &&
|
||||
material.attribute_dictionaries.any? { |dict| dict.name == MAT_DICTIONARY }
|
||||
end
|
||||
mat_hash = materials.collect { |material| [material.get_attribute(MAT_DICTIONARY, MAT_ID), material] }.to_h
|
||||
Materials.new(mat_hash)
|
||||
end
|
||||
|
||||
def initialize(material_hash = {})
|
||||
@materials_by_id = material_hash.freeze
|
||||
@id_by_materials = material_hash.collect { |id, material| [material, id] }.to_h.freeze
|
||||
freeze
|
||||
end
|
||||
|
||||
def add_material(id, material)
|
||||
old_material = @materials_by_id[id.to_sym]
|
||||
return self if material == old_material
|
||||
|
||||
new_material_hash = @materials_by_id.merge({ id.to_sym => material })
|
||||
Materials.new(new_material_hash)
|
||||
end
|
||||
|
||||
def by_id(id)
|
||||
@materials_by_id[id.to_sym]
|
||||
end
|
||||
|
||||
def material_id(material)
|
||||
@id_by_materials[material]
|
||||
end
|
||||
|
||||
def add_speckle_material
|
||||
@materials_by_id[MAT_ADD]
|
||||
end
|
||||
|
||||
def edit_speckle_material
|
||||
@materials_by_id[MAT_EDIT]
|
||||
end
|
||||
|
||||
def remove_speckle_material
|
||||
@materials_by_id[MAT_REMOVE]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../speckle_entities/speckle_entity'
|
||||
require_relative '../../constants/dict_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
# Operations related to {SketchupModel}.
|
||||
module SketchupModel
|
||||
# Reader model for sketchup model.
|
||||
module Reader
|
||||
# Reader module for speckle entities.
|
||||
module SpeckleEntitiesReader
|
||||
# @param entities [Sketchup::Entities] entities to collect speckle entities.
|
||||
def self.read(entities)
|
||||
speckle_entities = {}
|
||||
entities.each do |entity|
|
||||
speckle_entities[entity.persistent_id] = read_speckle_entity(entity) if speckle_entity?(entity)
|
||||
next unless entity.is_a?(Sketchup::Group) || entity.is_a?(Sketchup::ComponentInstance)
|
||||
|
||||
if speckle_entity?(entity.definition)
|
||||
speckle_entities[entity.definition.persistent_id] = read_speckle_entity(entity.definition)
|
||||
end
|
||||
definition_speckle_entities = read(entity.definition.entities)
|
||||
speckle_entities = speckle_entities.merge(definition_speckle_entities)
|
||||
end
|
||||
speckle_entities
|
||||
end
|
||||
|
||||
# @param entity [Sketchup::Entity] sketchup entity to read from attribute dictionary.
|
||||
def self.read_speckle_entity(entity)
|
||||
dict = entity.attribute_dictionaries.to_a.find { |d| d.name == SPECKLE_BASE_OBJECT }
|
||||
speckle_id = dict[:speckle_id]
|
||||
application_id = dict[:application_id]
|
||||
speckle_type = dict[:speckle_type]
|
||||
children = dict[:children]
|
||||
valid_stream_ids = dict[:valid_stream_ids]
|
||||
invalid_stream_ids = dict[:invalid_stream_ids]
|
||||
SpeckleEntities::SpeckleEntity.new(entity, speckle_id, application_id, speckle_type, children,
|
||||
valid_stream_ids, invalid_stream_ids)
|
||||
end
|
||||
|
||||
# @param entity [Sketchup::Entity] sketchup entity to check if it was speckle entity once.
|
||||
def self.speckle_entity?(entity)
|
||||
return false if entity.attribute_dictionaries.nil?
|
||||
return false if entity.attribute_dictionaries.to_a.empty?
|
||||
|
||||
entity.attribute_dictionaries.to_a.any? { |dict| dict.name == SPECKLE_BASE_OBJECT }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SpeckleConnector
|
||||
# Operations related to {SketchupModel}.
|
||||
module SketchupModel
|
||||
# Works directly with/on SketchUp Entities of different kinds (Groups, Faces, Edges, ...).
|
||||
module Utils
|
||||
# Static methods that work directly with Sketchup::Face objects
|
||||
class FaceUtils
|
||||
# A method that calculates the faces that are less than n-faces
|
||||
# away from the current face. For n=1, you would get the
|
||||
# neighbouring faces.
|
||||
# @param edge_ary [Array<Sketchup::Edge>] the edges to look for n-adjacent faces
|
||||
# @param n_adjacent [Integer] the distance from the edges (in number of faces)
|
||||
# @return [Array<Sketchup::Face>] the faces that are n faces away from the given edges
|
||||
def self.near_faces(edges_ary, n_adjacent = 1)
|
||||
# get all the faces that are not more than a face away to the current face.
|
||||
edges_ary = edges_ary.select(&:valid?)
|
||||
adj_faces = []
|
||||
n_adjacent.times do |_i|
|
||||
new_faces = edges_ary.collect(&:faces).flatten.uniq
|
||||
adj_faces += new_faces
|
||||
edges_ary = new_faces.collect(&:edges).flatten.uniq - edges_ary
|
||||
end
|
||||
adj_faces.uniq
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,89 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SpeckleConnector
|
||||
# Operations related to {SketchupModel}.
|
||||
module SketchupModel
|
||||
# Works directly with/on SketchUp Entities of different kinds (Groups, Faces, Edges, ...).
|
||||
module Utils
|
||||
# Static methods to do plane calculations with sketchup geom objects like Point3d and Vector3d.
|
||||
class Plane
|
||||
ORTHOGONAL_TOLERANCE = 1e-5
|
||||
LENGTH_TOLERANCE = 1e-8
|
||||
|
||||
# Create plane from 3 points
|
||||
# @param origin [Geom::Point3d] the point on the plane that wil become the origin of the local coordinate system
|
||||
# @param point_1 [Geom::Point3d] the point that defines first direction
|
||||
# @param point_2 [Geom::Point3d] the third point on the plane
|
||||
# @return [Plane] the parametrization of the plane that goes through the given points
|
||||
def self.from_points(origin, point_1, point_2)
|
||||
direction_x = origin.vector_to(point_1).normalize
|
||||
direction_x = direction_x.normalize
|
||||
normal = direction_x.cross(origin.vector_to(point_2))
|
||||
direction_y = direction_x.cross(normal.normalize)
|
||||
new(origin: origin, direction_u: direction_x, direction_v: direction_y)
|
||||
end
|
||||
|
||||
# @return [Geom::Vector3d] the direction of the u-axis on the plane
|
||||
attr_reader :direction_u
|
||||
|
||||
# @return [Geom::Vector3d] the direction of the v-axis on the plane
|
||||
attr_reader :direction_v
|
||||
|
||||
# @return [Geom::Point3d] the origin of the local coordinate system on the plane
|
||||
attr_reader :origin
|
||||
|
||||
# @param origin [Geom::Point3d] the origin of the coordinate system on the plane
|
||||
# @param direction_u [Geom::Vector3d] the direction of the x-axis
|
||||
# @param direction_v [Geom::Vector3d] the direction of the y-axis
|
||||
def initialize(origin:, direction_u:, direction_v:)
|
||||
@origin = origin
|
||||
raise ArgumentError, 'directions must me orthogonal' if direction_u.dot(direction_v) > ORTHOGONAL_TOLERANCE
|
||||
raise ArgumentError, 'directions must be of length 1' if (direction_u.length - 1).abs > LENGTH_TOLERANCE
|
||||
raise ArgumentError, 'directions must be of length 1' if (direction_v.length - 1).abs > LENGTH_TOLERANCE
|
||||
|
||||
@direction_u = direction_u
|
||||
@direction_v = direction_v
|
||||
end
|
||||
|
||||
# Get the point object in global coordinates for the point on the plane with local coordinates (u,v).
|
||||
# @param coordinate_u [Float] the u-coordinate on the plane
|
||||
# @param coordinate_v [Float] the v-coordinate on the plane
|
||||
# @return [Geom::Point3d] the point in space that corresponds to the given (u, v) coordinates
|
||||
def point_at(coordinate_u, coordinate_v)
|
||||
scaled_direction_u = Geom::Vector3d.new(direction_u.x * coordinate_u,
|
||||
direction_u.y * coordinate_u,
|
||||
direction_u.z * coordinate_u)
|
||||
scaled_direction_v = Geom::Vector3d.new(direction_v.x * coordinate_v,
|
||||
direction_v.y * coordinate_v,
|
||||
direction_v.z * coordinate_v)
|
||||
origin + scaled_direction_u + scaled_direction_v
|
||||
end
|
||||
|
||||
# Find local (u, v) coordinates of the projection of the given point to the plane
|
||||
# @param point [Geom::Point3d] the point that will be projected to the plane
|
||||
# @return [(Float, Float)] the local coordinates on the plane that correspond to the projected point
|
||||
def plane_coordinates(point)
|
||||
origin_to_point = origin.vector_to(point)
|
||||
coordinate_u = origin_to_point.dot(direction_u)
|
||||
coordinate_v = origin_to_point.dot(direction_v)
|
||||
[coordinate_u, coordinate_v]
|
||||
end
|
||||
|
||||
# Project a given point to the plane
|
||||
# @param point [Geom::Point3d] the point that will be projected to the plane
|
||||
# @return [Geom::Point3d] the projected point on the plane
|
||||
def project_to_plane(point)
|
||||
coordinate_u, coordinate_v = plane_coordinates(point)
|
||||
point_at(coordinate_u, coordinate_v)
|
||||
end
|
||||
|
||||
# Check if the given point lies on the plane
|
||||
# @param point [Geom::Point3d] the point to check
|
||||
# @return [Boolean] whether the point lies on the plane
|
||||
def on_plane?(point)
|
||||
point.distance(project_to_plane(point)).to_m < LENGTH_TOLERANCE
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,38 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../immutable/immutable'
|
||||
require_relative '../convertors/units'
|
||||
require_relative '../speckle_objects/base'
|
||||
require_relative '../speckle_entities/speckle_line_entity'
|
||||
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleEntities
|
||||
# Speckle base entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
|
||||
class SpeckleBaseEntity
|
||||
include Immutable::ImmutableUtils
|
||||
# @return [Sketchup::Entity] sketchup entity represents {SpeckleEntity} on the model.
|
||||
attr_reader :sketchup_entity
|
||||
|
||||
# @return [SpeckleObjects::Base] speckle object that represented on server.
|
||||
attr_reader :speckle_object
|
||||
|
||||
# @return [Integer] application id of the sketchup entity.
|
||||
attr_reader :application_id
|
||||
|
||||
# @param sketchup_entity [Sketchup::Entity] sketchup entity represents {SpeckleEntity} on the model.
|
||||
def initialize(sketchup_model, sketchup_entity)
|
||||
@sketchup_entity = sketchup_entity
|
||||
@application_id = @sketchup_entity.persistent_id
|
||||
@speckle_object = SpeckleObjects::Base.new
|
||||
su_unit = sketchup_model.options['UnitsOptions']['LengthUnit']
|
||||
@units = Converters::SKETCHUP_UNITS[su_unit]
|
||||
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.write_initial_base_data(@sketchup_entity)
|
||||
end
|
||||
|
||||
def valid?
|
||||
sketchup_entity.valid?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,19 +1,123 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../speckle_entities/speckle_line_entity'
|
||||
require_relative 'speckle_entity_status'
|
||||
require_relative '../immutable/immutable'
|
||||
require_relative '../convertors/units'
|
||||
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleEntities
|
||||
# Speckle entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
|
||||
module SpeckleEntity
|
||||
def self.with_converted(skp_model, skp_entity)
|
||||
# return the same object if it is already SpeckleEntity
|
||||
return skp_entity if skp_entity.is_a?(SpeckleEntity)
|
||||
return SpeckleBlockEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::Group)
|
||||
return SpeckleBlockEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::ComponentInstance)
|
||||
return SpeckleMeshEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::Face)
|
||||
# Speckle base entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
|
||||
class SpeckleEntity
|
||||
include Immutable::ImmutableUtils
|
||||
# @return [Sketchup::Entity] Sketchup Entity represents {SpeckleEntity} on the model.
|
||||
attr_reader :sketchup_entity
|
||||
|
||||
SpeckleLineEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::Edge)
|
||||
# @return [String] Speckle object type.
|
||||
attr_reader :speckle_type
|
||||
|
||||
# @return [Integer] application id of the Sketchup Entity.
|
||||
attr_reader :application_id
|
||||
|
||||
# @return [String] id of the Speckle Base Object
|
||||
attr_reader :id
|
||||
|
||||
# @return [Integer] total children count of the Speckle Base Object
|
||||
attr_reader :total_children_count
|
||||
|
||||
# @return [Hash{String=>SpeckleObjects::Base}] Speckle objects belongs to edge
|
||||
attr_reader :speckle_children_objects
|
||||
|
||||
# @return [Array<String>] speckle entity that valid on streams
|
||||
attr_reader :valid_stream_ids
|
||||
|
||||
# @return [Array<String>] speckle entity that invalid on streams
|
||||
attr_reader :invalid_stream_ids
|
||||
|
||||
# @return [SpeckleEntityStatus] current status of the Speckle Entity.
|
||||
attr_reader :status
|
||||
|
||||
attr_reader :source_material, :active_diffing_stream_id
|
||||
|
||||
# @param sketchup_entity [Sketchup::Entity] sketchup entity represents {SpeckleEntity} on the model.
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def initialize(sketchup_entity, speckle_id, application_id, speckle_type, children, valid_stream_ids,
|
||||
invalid_stream_ids = [])
|
||||
@status = SpeckleEntityStatus::UP_TO_DATE
|
||||
@source_material = sketchup_entity.material
|
||||
@active_diffing_stream_id = nil
|
||||
@valid_stream_ids = valid_stream_ids
|
||||
@invalid_stream_ids = invalid_stream_ids
|
||||
@sketchup_entity = sketchup_entity
|
||||
@application_id = application_id
|
||||
@id = speckle_id
|
||||
@total_children_count = children.length
|
||||
@speckle_type = speckle_type
|
||||
@speckle_children_objects = children
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
def write_initial_base_data
|
||||
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler
|
||||
.write_initial_base_data(@sketchup_entity, application_id, id, speckle_type,
|
||||
@speckle_children_objects, valid_stream_ids.first)
|
||||
end
|
||||
|
||||
def with_up_to_date
|
||||
with(:@status => SpeckleEntityStatus::UP_TO_DATE)
|
||||
end
|
||||
|
||||
def with_invalid
|
||||
valid = valid_stream_ids
|
||||
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, VALID_STREAM_IDS, [])
|
||||
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, INVALID_STREAM_IDS, valid)
|
||||
with(:@valid_stream_ids => [], :@invalid_stream_ids => valid)
|
||||
end
|
||||
|
||||
def activate_diffing(stream_id, material)
|
||||
sketchup_entity.material = material
|
||||
with(:@active_diffing_stream_id => stream_id)
|
||||
end
|
||||
|
||||
def deactivate_diffing
|
||||
sketchup_entity.material = @source_material
|
||||
with(:@active_diffing_stream_id => nil)
|
||||
end
|
||||
|
||||
def change_material(material)
|
||||
sketchup_entity.material = material
|
||||
end
|
||||
|
||||
def revert_material
|
||||
sketchup_entity.material = source_material
|
||||
end
|
||||
|
||||
def with_valid_stream_id(stream_id)
|
||||
return self if valid_stream_ids.include?(stream_id)
|
||||
|
||||
invalid_ids = @invalid_stream_ids.include?(stream_id) ? @invalid_stream_ids - [stream_id] : @invalid_stream_ids
|
||||
valid_ids = valid_stream_ids + [stream_id]
|
||||
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, VALID_STREAM_IDS, valid_ids)
|
||||
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, INVALID_STREAM_IDS, invalid_ids)
|
||||
|
||||
# if sketchup_entity.is_a?(Sketchup::Group) || sketchup_entity.is_a?(Sketchup::ComponentInstance)
|
||||
# sketchup_entity.definition.set_attribute(SPECKLE_BASE_OBJECT, VALID_STREAM_IDS, valid_ids)
|
||||
# sketchup_entity.definition.set_attribute(SPECKLE_BASE_OBJECT, INVALID_STREAM_IDS, invalid_ids)
|
||||
# end
|
||||
|
||||
with(:@valid_stream_ids => @valid_stream_ids + [stream_id], :@invalid_stream_ids => invalid_ids)
|
||||
end
|
||||
|
||||
def with_edited
|
||||
with(:@status => SpeckleEntityStatus::EDITED)
|
||||
end
|
||||
|
||||
def with_removed
|
||||
with(:@status => SpeckleEntityStatus::REMOVED)
|
||||
end
|
||||
|
||||
def valid?
|
||||
sketchup_entity.valid?
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../immutable/immutable'
|
||||
require_relative '../convertors/units'
|
||||
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleEntities
|
||||
module SpeckleEntityStatus
|
||||
# Speckle Entity created first time with {Sketchup::Entity} before sent to server.
|
||||
UP_TO_DATE = 0
|
||||
# {Sketchup::Entity} that corresponds to Speckle Entity is edited after created by user.
|
||||
EDITED = 1
|
||||
# {Sketchup::Entity} that corresponds to Speckle Entity is removed.
|
||||
REMOVED = 2
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,25 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'speckle_base_entity'
|
||||
require_relative '../immutable/immutable'
|
||||
require_relative '../speckle_objects/geometry/line'
|
||||
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleEntities
|
||||
# Speckle line entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
|
||||
class SpeckleLineEntity < SpeckleBaseEntity
|
||||
include Immutable::ImmutableUtils
|
||||
|
||||
# @return [SpeckleObjects::Geometry::Line] speckle line object
|
||||
attr_reader :speckle_object
|
||||
|
||||
def initialize(sketchup_model, sketchup_edge)
|
||||
super(sketchup_model, sketchup_edge)
|
||||
@speckle_object = SpeckleObjects::Geometry::Line.from_edge(sketchup_edge, units)
|
||||
end
|
||||
|
||||
alias sketchup_edge sketchup_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,119 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../base'
|
||||
require_relative '../../constants/type_constants'
|
||||
require_relative '../../speckle_objects/geometry/point'
|
||||
require_relative '../../speckle_objects/geometry/vector'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleObjects
|
||||
module BuiltElements
|
||||
# View3d object represents scenes on Sketchup.
|
||||
class View3d < Base
|
||||
SPECKLE_TYPE = OBJECTS_BUILTELEMENTS_VIEW3D
|
||||
|
||||
# @param name [String] name of the scene
|
||||
# @param origin [SpeckleObjects::Geometry::Point] origin (eye) of the view.
|
||||
# @param target [SpeckleObjects::Geometry::Point] target of the view.
|
||||
# @param direction [SpeckleObjects::Geometry::Vector] direction of the view from eye to target.
|
||||
# @param up_direction [SpeckleObjects::Geometry::Vector] up direction of the view.
|
||||
# @param is_perspective [Boolean] whether view is perspective or not.
|
||||
# @param lens [Boolean] fov value of the view camera.
|
||||
# @param units [String] units of the camera.
|
||||
# @param application_id [String] application_id of the view.
|
||||
# @param update_properties [Hash{Symbol=>boolean}] properties of the view.
|
||||
# @param rendering_options [Hash{Symbol=>boolean}] rendering options of the view.
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def initialize(name, origin, target, direction, up_direction,
|
||||
is_perspective, lens, units, application_id, update_properties, rendering_options)
|
||||
super(
|
||||
speckle_type: SPECKLE_TYPE,
|
||||
total_children_count: 0,
|
||||
application_id: application_id,
|
||||
id: nil
|
||||
)
|
||||
self[:name] = name
|
||||
self[:origin] = origin
|
||||
self[:target] = target
|
||||
self[:forwardDirection] = direction
|
||||
self[:upDirection] = up_direction
|
||||
self[:isOrthogonal] = !is_perspective
|
||||
self[:lens] = lens
|
||||
self[:units] = units
|
||||
self[:update_properties] = update_properties
|
||||
self[:rendering_options] = rendering_options
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
# @param obj [Hash] commit object.
|
||||
# @param sketchup_model [Sketchup::Model] active sketchup model.
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
# rubocop:disable Metrics/PerceivedComplexity
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def self.to_native(obj, sketchup_model)
|
||||
views = collect_views(obj)
|
||||
return if views.empty?
|
||||
|
||||
views.each do |view|
|
||||
next unless view['speckle_type'] == 'Objects.BuiltElements.View:Objects.BuiltElements.View3D'
|
||||
|
||||
name = view['name'] || view['id']
|
||||
next if sketchup_model.pages.any? { |page| page.name == name }
|
||||
|
||||
origin = view['origin']
|
||||
target = view['target']
|
||||
lens = view['lens'] || 50
|
||||
origin = SpeckleObjects::Geometry::Point.to_native(origin['x'], origin['y'], origin['z'], origin['units'])
|
||||
target = SpeckleObjects::Geometry::Point.to_native(target['x'], target['y'], target['z'], target['units'])
|
||||
# Set camera position before creating scene on it.
|
||||
my_camera = Sketchup::Camera.new(origin, target, [0, 0, 1], !view['isOrthogonal'], lens)
|
||||
sketchup_model.active_view.camera = my_camera
|
||||
sketchup_model.pages.add(name)
|
||||
page = sketchup_model.pages[name]
|
||||
set_page_update_properties(page, view['update_properties']) if view['update_properties']
|
||||
set_rendering_options(page.rendering_options, view['rendering_options']) if view['rendering_options']
|
||||
rescue StandardError => e
|
||||
puts("Failed to convert view (name: #{name})")
|
||||
puts(e)
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
# rubocop:enable Metrics/PerceivedComplexity
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
# @param page [Sketchup::Page] scene to update -update properties-
|
||||
def self.set_page_update_properties(page, update_properties)
|
||||
update_properties.each do |prop, value|
|
||||
page.instance_variable_set(:"@#{prop}", value)
|
||||
end
|
||||
end
|
||||
|
||||
# @param rendering_options [Sketchup::RenderingOptions] rendering options of scene (page)
|
||||
def self.set_rendering_options(rendering_options, speckle_rendering_options)
|
||||
speckle_rendering_options.each do |prop, value|
|
||||
next if rendering_options[prop].nil?
|
||||
|
||||
rendering_options[prop] = if value.is_a?(Hash)
|
||||
SpeckleObjects::Others::Color.to_native(value)
|
||||
else
|
||||
value
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def self.collect_views(obj)
|
||||
views = []
|
||||
views += obj.filter_map do |_key, value|
|
||||
if value.is_a?(Array) &&
|
||||
value.any? { |v| v['speckle_type'] == OBJECTS_BUILTELEMENTS_VIEW3D }
|
||||
value
|
||||
end
|
||||
end
|
||||
views.flatten.select { |view| view['speckle_type'] == OBJECTS_BUILTELEMENTS_VIEW3D }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,6 +5,7 @@ require_relative 'point'
|
||||
require_relative 'bounding_box'
|
||||
require_relative '../base'
|
||||
require_relative '../primitive/interval'
|
||||
require_relative '../../sketchup_model/dictionary/dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleObjects
|
||||
@@ -20,7 +21,7 @@ module SpeckleConnector
|
||||
# @param units [String] units of the speckle line.
|
||||
# @param application_id [String, nil] entity id of the {Sketchup::Edge} that represents to the speckle line.
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def initialize(start_pt:, end_pt:, domain:, bbox:, units:, application_id: nil)
|
||||
def initialize(start_pt:, end_pt:, domain:, bbox:, units:, sketchup_attributes: {}, application_id: nil)
|
||||
super(
|
||||
speckle_type: 'Objects.Geometry.Line',
|
||||
total_children_count: 0,
|
||||
@@ -32,11 +33,17 @@ module SpeckleConnector
|
||||
self[:domain] = domain
|
||||
self[:bbox] = bbox
|
||||
self[:units] = units
|
||||
self[:sketchup_attributes] = sketchup_attributes if sketchup_attributes.any?
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
# @param edge [Sketchup::Edge] edge to convert line.
|
||||
def self.from_edge(edge, units)
|
||||
def self.from_edge(edge, units, model_preferences)
|
||||
dictionaries = {}
|
||||
if model_preferences[:include_entity_attributes] && model_preferences[:include_edge_entity_attributes]
|
||||
dictionaries = SketchupModel::Dictionary::DictionaryHandler.attribute_dictionaries_to_speckle(edge)
|
||||
end
|
||||
att = dictionaries.any? ? { dictionaries: dictionaries } : {}
|
||||
start_pt = Geometry::Point.from_vertex(edge.start.position, units)
|
||||
end_pt = Geometry::Point.from_vertex(edge.end.position, units)
|
||||
domain = Primitive::Interval.from_numeric(0, Float(edge.length), units)
|
||||
@@ -46,16 +53,18 @@ module SpeckleConnector
|
||||
end_pt: end_pt,
|
||||
domain: domain,
|
||||
bbox: bbox,
|
||||
application_id: edge.persistent_id.to_s,
|
||||
units: units
|
||||
units: units,
|
||||
sketchup_attributes: att,
|
||||
application_id: edge.persistent_id.to_s
|
||||
)
|
||||
end
|
||||
|
||||
# @param _state [States::State] state of the application.
|
||||
# @param line [Object] object represents Speckle line.
|
||||
# @param layer [Sketchup::Layer] layer to add {Sketchup::Edge} into it.
|
||||
# @param entities [Sketchup::Entities] entities collection to add {Sketchup::Edge} into it.
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def self.to_native(line, layer, entities)
|
||||
def self.to_native(state, line, layer, entities, &_convert_to_native)
|
||||
if line.key?('value')
|
||||
values = line['value']
|
||||
points = values.each_slice(3).to_a.map { |pt| Point.to_native(pt[0], pt[1], pt[2], line['units']) }
|
||||
@@ -66,7 +75,14 @@ module SpeckleConnector
|
||||
end_pt = Point.to_native(line['end']['x'], line['end']['y'], line['end']['z'], line['units'])
|
||||
edges = entities.add_edges(start_pt, end_pt)
|
||||
end
|
||||
edges.each { |edge| edge.layer = layer }
|
||||
edges.each do |edge|
|
||||
edge.layer = layer
|
||||
unless line['sketchup_attributes'].nil?
|
||||
SketchupModel::Dictionary::DictionaryHandler
|
||||
.attribute_dictionaries_to_native(edge, line['sketchup_attributes']['dictionaries'])
|
||||
end
|
||||
end
|
||||
return state, edges
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ require_relative '../base'
|
||||
require_relative '../geometry/bounding_box'
|
||||
require_relative '../other/render_material'
|
||||
require_relative '../../convertors/clean_up'
|
||||
require_relative '../../sketchup_model/dictionary/dictionary_handler'
|
||||
require_relative '../../sketchup_model/utils/plane_utils'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleObjects
|
||||
@@ -13,35 +15,68 @@ module SpeckleConnector
|
||||
class Mesh < Base
|
||||
SPECKLE_TYPE = 'Objects.Geometry.Mesh'
|
||||
|
||||
# @return [Array<Geom::Point3d>] points that construct mesh.
|
||||
attr_accessor :vertices
|
||||
|
||||
# @return [Array] polygons
|
||||
attr_accessor :polygons
|
||||
|
||||
# @return [String] speckle units.
|
||||
attr_reader :units
|
||||
|
||||
# @param units [String] units of the speckle mesh.
|
||||
# @param render_material [Other::RenderMaterial, nil] render material of the speckle mesh.
|
||||
# @param bbox [Geometry::BoundingBox] bounding box speckle object of the speckle mesh.
|
||||
# @param vertices [Array] vertices of the speckle mesh.
|
||||
# @param faces [Array] faces of the speckle mesh.
|
||||
# @param face_edge_flags [Array] face edge flags of the speckle mesh.
|
||||
# @param sketchup_attributes [Hash] additional information about speckle mesh.
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def initialize(units:, render_material:, bbox:, vertices:, faces:, face_edge_flags:, sketchup_attributes:)
|
||||
def initialize(units:, render_material:, vertices:, faces:, sketchup_attributes:, application_id: nil)
|
||||
super(
|
||||
speckle_type: SPECKLE_TYPE,
|
||||
total_children_count: 0,
|
||||
application_id: nil,
|
||||
application_id: application_id,
|
||||
id: nil
|
||||
)
|
||||
@vertices = []
|
||||
@polygons = []
|
||||
@units = units
|
||||
self[:units] = units
|
||||
self[:renderMaterial] = render_material
|
||||
self[:bbox] = bbox
|
||||
# self[:bbox] = bbox
|
||||
self[:'@(31250)vertices'] = vertices
|
||||
self[:'@(62500)faces'] = faces
|
||||
self[:'@(31250)faceEdgeFlags'] = face_edge_flags
|
||||
self[:sketchup_attributes] = sketchup_attributes
|
||||
self[:sketchup_attributes] = sketchup_attributes if sketchup_attributes.any?
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
# Checks 4 points are planar or not.
|
||||
def self.check_4_points_planar(points)
|
||||
plane = SketchupModel::Utils::Plane.from_points(points[0], points[1], points[2])
|
||||
plane.on_plane?(points[3])
|
||||
end
|
||||
|
||||
# Add quad mesh to sketchup native mesh by checking planarity.
|
||||
# @param native_mesh [Geom::Mesh] sketchup mesh to convert them later faces.
|
||||
# @param polygon_points [Array<Geom::Point3d>] sketchup points to add them with polygon to mesh.
|
||||
def self.add_quad_mesh(native_mesh, polygon_points)
|
||||
is_planar = check_4_points_planar(polygon_points)
|
||||
if is_planar
|
||||
native_mesh.add_polygon(polygon_points)
|
||||
else
|
||||
native_mesh.add_polygon([polygon_points[0], polygon_points[1], polygon_points[2]])
|
||||
native_mesh.add_polygon([polygon_points[0], polygon_points[2], polygon_points[3]])
|
||||
end
|
||||
end
|
||||
|
||||
# @param entities [Sketchup::Entities] entities to add
|
||||
def self.to_native(sketchup_model, mesh, layer, entities)
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
# rubocop:disable Metrics/PerceivedComplexity:
|
||||
def self.to_native(state, mesh, layer, entities, &convert_to_native)
|
||||
model_preferences = state.user_state.preferences[:model]
|
||||
# Get soft? flag of {Sketchup::Edge} object to understand smoothness of edge.
|
||||
is_soften = get_soften_setting(mesh)
|
||||
is_soften = get_soften_setting(mesh, entities)
|
||||
smooth_flags = is_soften ? 4 : 1
|
||||
# Get native points to add polygon into native mesh.
|
||||
points = get_native_points(mesh)
|
||||
@@ -53,37 +88,84 @@ module SpeckleConnector
|
||||
# 0 -> 3, 1 -> 4 to preserve backwards compatibility
|
||||
num_pts += 3 if num_pts < 3
|
||||
indices = faces.shift(num_pts)
|
||||
native_mesh.add_polygon(indices.map { |index| points[index] })
|
||||
polygon_points = indices.map { |index| points[index] }
|
||||
# Quad mesh
|
||||
if num_pts == 4
|
||||
add_quad_mesh(native_mesh, polygon_points)
|
||||
else
|
||||
native_mesh.add_polygon(polygon_points)
|
||||
end
|
||||
end
|
||||
material = Other::RenderMaterial.to_native(sketchup_model, mesh['renderMaterial'])
|
||||
entities.add_faces_from_mesh(native_mesh, smooth_flags, material)
|
||||
state, _materials = Other::RenderMaterial.to_native(state, mesh['renderMaterial'],
|
||||
layer, entities, &convert_to_native)
|
||||
# Find and assign material if exist
|
||||
unless mesh['renderMaterial'].nil?
|
||||
material_name = mesh['renderMaterial']['name'] || mesh['renderMaterial']['id']
|
||||
# Retrieve material from state
|
||||
material = state.sketchup_state.materials.by_id(material_name)
|
||||
end
|
||||
|
||||
# Add faces from mesh with material and smooth setting
|
||||
entities.add_faces_from_mesh(native_mesh, smooth_flags, material, material)
|
||||
added_faces = entities.grep(Sketchup::Face).last(native_mesh.polygons.length)
|
||||
added_faces.each { |face| face.layer = layer }
|
||||
added_faces.each do |face|
|
||||
face.layer = layer
|
||||
# Smooth edges if they already soft
|
||||
face.edges.each { |edge| edge.smooth = true if edge.soft? }
|
||||
unless mesh['sketchup_attributes'].nil?
|
||||
SketchupModel::Dictionary::DictionaryHandler
|
||||
.attribute_dictionaries_to_native(face, mesh['sketchup_attributes']['dictionaries'])
|
||||
end
|
||||
end
|
||||
# Merge only added faces in this scope
|
||||
Converters::CleanUp.merge_coplanar_faces(added_faces)
|
||||
native_mesh
|
||||
if model_preferences[:merge_coplanar_faces]
|
||||
added_faces = Converters::CleanUp.merge_coplanar_faces(added_faces)
|
||||
end
|
||||
return state, added_faces
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
# rubocop:enable Metrics/PerceivedComplexity:
|
||||
|
||||
# @param face [Sketchup::Face] face to convert mesh
|
||||
def self.from_face(face, units)
|
||||
mesh = face.loops.count > 1 ? face.mesh : nil
|
||||
# rubocop:disable Style/MultilineTernaryOperator
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
def self.from_face(face, units, model_preferences)
|
||||
dictionaries = {}
|
||||
if model_preferences[:include_entity_attributes] && model_preferences[:include_face_entity_attributes]
|
||||
dictionaries = SketchupModel::Dictionary::DictionaryHandler.attribute_dictionaries_to_speckle(face)
|
||||
end
|
||||
has_any_soften_edge = face.edges.any?(&:soft?)
|
||||
Mesh.new(
|
||||
att = dictionaries.any? ? { is_soften: has_any_soften_edge, dictionaries: dictionaries }
|
||||
: { is_soften: has_any_soften_edge }
|
||||
speckle_mesh = Mesh.new(
|
||||
units: units,
|
||||
render_material: face.material.nil? ? nil : Other::RenderMaterial.from_material(face.material),
|
||||
bbox: Geometry::BoundingBox.from_bounds(face.bounds, units),
|
||||
vertices: mesh.nil? ? face_vertices_to_array(face, units) : mesh_points_to_array(mesh, units),
|
||||
faces: mesh.nil? ? face_indices_to_array(face, 0) : mesh_faces_to_array(mesh, -1),
|
||||
face_edge_flags: mesh.nil? ? face_edge_flags_to_array(face) : mesh_edge_flags_to_array(mesh),
|
||||
sketchup_attributes: { is_soften: has_any_soften_edge }
|
||||
render_material: face.material.nil? && face.back_material.nil? ? nil : Other::RenderMaterial
|
||||
.from_material(face.material || face.back_material),
|
||||
# bbox: Geometry::BoundingBox.from_bounds(face.bounds, units),
|
||||
vertices: [],
|
||||
faces: [],
|
||||
sketchup_attributes: att,
|
||||
application_id: face.persistent_id
|
||||
)
|
||||
speckle_mesh.face_to_mesh(face)
|
||||
speckle_mesh.update_mesh
|
||||
speckle_mesh
|
||||
end
|
||||
# rubocop:enable Style/MultilineTernaryOperator
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
|
||||
def face_to_mesh(face)
|
||||
mesh = face.loops.count > 1 ? face.mesh : nil
|
||||
mesh.nil? ? face_vertices_to_array(face) : mesh_points_to_array(mesh)
|
||||
mesh.nil? ? face_indices_to_array(face) : mesh_faces_to_array(mesh)
|
||||
end
|
||||
|
||||
# get a flat array of vertices from a list of sketchup vertices
|
||||
def self.face_vertices_to_array(face, units)
|
||||
# Collects indexed Sketchup vertices into flat array for Speckle use.
|
||||
def vertices_to_array(units)
|
||||
pts_array = []
|
||||
face.vertices.each do |v|
|
||||
pt = v.position
|
||||
vertices.each do |pt|
|
||||
pts_array.push(Geometry.length_to_speckle(pt[0], units),
|
||||
Geometry.length_to_speckle(pt[1], units),
|
||||
Geometry.length_to_speckle(pt[2], units))
|
||||
@@ -91,57 +173,77 @@ module SpeckleConnector
|
||||
pts_array
|
||||
end
|
||||
|
||||
# get a flat array of vertices from a sketchup polygon mesh
|
||||
def self.mesh_points_to_array(mesh, units)
|
||||
pts_array = []
|
||||
def update_mesh
|
||||
# puts "Vertex count on mesh #{vertices.length}"
|
||||
self['@(31250)vertices'] = vertices_to_array(units)
|
||||
self[:'@(62500)faces'] = polygons
|
||||
end
|
||||
|
||||
# Get a flat array of vertices from a list of sketchup vertices
|
||||
# @param face [Sketchup::Face] face to get vertices.
|
||||
def face_vertices_to_array(face)
|
||||
face.vertices.each do |v|
|
||||
pt = v.position
|
||||
# FIXME: Enable previous line when viewer supports shared vertices
|
||||
# vertices.push(pt) unless vertices.any? { |point| point == pt }
|
||||
vertices.push(pt)
|
||||
end
|
||||
end
|
||||
|
||||
# Get a flat array of face indices from a sketchup face
|
||||
def face_indices_to_array(face)
|
||||
polygons.push(face.vertices.count)
|
||||
face.vertices.each do |v|
|
||||
pt = v.position
|
||||
# FIXME: Enable previous line when viewer supports shared vertices
|
||||
# global_vertex_index = vertices.reverse.find_index(pt)
|
||||
global_vertex_index = vertices.length - vertices.reverse.find_index(pt) - 1
|
||||
polygons.push(global_vertex_index)
|
||||
end
|
||||
end
|
||||
|
||||
# Get a flat array of vertices from a sketchup polygon mesh
|
||||
# @param mesh [Geom::PolygonMesh] mesh to get points.
|
||||
def mesh_points_to_array(mesh)
|
||||
mesh.points.each do |pt|
|
||||
pts_array.push(
|
||||
Geometry.length_to_speckle(pt[0], units),
|
||||
Geometry.length_to_speckle(pt[1], units),
|
||||
Geometry.length_to_speckle(pt[2], units)
|
||||
)
|
||||
# FIXME: Enable previous line when viewer supports shared vertices
|
||||
# vertices.push(pt) unless vertices.any? { |point| point == pt }
|
||||
vertices.push(pt)
|
||||
end
|
||||
pts_array
|
||||
end
|
||||
|
||||
# get a flat array of face indices from a sketchup face
|
||||
def self.face_indices_to_array(face, offset)
|
||||
face_array = [face.vertices.count]
|
||||
face_array.push(*face.vertices.count.times.map { |index| index + offset })
|
||||
face_array
|
||||
end
|
||||
|
||||
# get an array of face indices from a sketchup polygon mesh
|
||||
def self.mesh_faces_to_array(mesh, offset = 0)
|
||||
faces = []
|
||||
# Get an array of face indices from a sketchup polygon mesh
|
||||
# @param mesh [Geom::PolygonMesh] mesh to convert into polygons.
|
||||
def mesh_faces_to_array(mesh)
|
||||
mesh.polygons.each do |poly|
|
||||
faces.push(
|
||||
poly.count, *poly.map { |index| index.abs + offset }
|
||||
)
|
||||
global_polygon_array = [poly.count]
|
||||
poly.each do |index|
|
||||
# FIXME: Enable previous line when viewer supports shared vertices
|
||||
# global_vertex_index = vertices.reverse.find_index(mesh.points[index.abs - 1])
|
||||
global_vertex_index = vertices.length - vertices.reverse.find_index(mesh.points[index.abs - 1]) - 1
|
||||
global_polygon_array.push(global_vertex_index)
|
||||
end
|
||||
polygons.push(*global_polygon_array)
|
||||
end
|
||||
faces
|
||||
end
|
||||
|
||||
def self.face_edge_flags_to_array(face)
|
||||
face.outer_loop.edges.map(&:soft?)
|
||||
end
|
||||
DEFINITIONS_WILL_BE_HARD_EDGE = %w[
|
||||
Walls
|
||||
Floors
|
||||
Stairs
|
||||
Structural Foundations
|
||||
Doors
|
||||
Windows
|
||||
].freeze
|
||||
|
||||
def self.mesh_edge_flags_to_array(mesh)
|
||||
edge_flags = []
|
||||
mesh.polygons.each do |poly|
|
||||
edge_flags.push(
|
||||
*poly.map(&:negative?)
|
||||
)
|
||||
# @param mesh [Object] speckle mesh object
|
||||
# @param entities [Sketchup::Entities] sketchup entities that mesh will be created in it as face.
|
||||
def self.get_soften_setting(mesh, entities)
|
||||
unless mesh['sketchup_attributes'].nil?
|
||||
return mesh['sketchup_attributes']['is_soften'].nil? ? true : mesh['sketchup_attributes']['is_soften']
|
||||
end
|
||||
edge_flags
|
||||
end
|
||||
|
||||
def self.get_soften_setting(mesh)
|
||||
if mesh['sketchup_attributes'].nil?
|
||||
true
|
||||
else
|
||||
mesh['sketchup_attributes']['is_soften'].nil? ? true : mesh['sketchup_attributes']['is_soften']
|
||||
end
|
||||
return DEFINITIONS_WILL_BE_HARD_EDGE.none? { |def_name| entities.parent.name.include?(def_name) }
|
||||
end
|
||||
|
||||
def self.get_native_points(mesh)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
|
||||
require_relative 'length'
|
||||
require_relative '../base'
|
||||
require_relative '../../typescript/typescript_object'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleObjects
|
||||
@@ -28,6 +27,17 @@ module SpeckleConnector
|
||||
self[:units] = units
|
||||
end
|
||||
|
||||
# Compare this point with other point those are reference to same coordinate.
|
||||
# @param other [SpeckleObjects::Geometry::Point] other point to compare.
|
||||
def ==(other, tolerance: 1e-15)
|
||||
return false if (self[:x] - other[:x]).abs > tolerance
|
||||
return false if (self[:y] - other[:y]).abs > tolerance
|
||||
return false if (self[:z] - other[:z]).abs > tolerance
|
||||
return false if self[:units] != other[:units]
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
# @param vertex [Geom::Point3d] sketchup point to convert speckle point.
|
||||
# @param units [String] unit of the point.
|
||||
def self.from_vertex(vertex, units)
|
||||
|
||||
@@ -7,6 +7,7 @@ require_relative '../base'
|
||||
require_relative '../geometry/point'
|
||||
require_relative '../geometry/mesh'
|
||||
require_relative '../geometry/bounding_box'
|
||||
require_relative '../../sketchup_model/dictionary/dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleObjects
|
||||
@@ -16,11 +17,12 @@ module SpeckleConnector
|
||||
SPECKLE_TYPE = 'Objects.Other.BlockDefinition'
|
||||
|
||||
# @param geometry [Object] geometric definition of the block.
|
||||
# @param base_point [Geometry::Point] base point of the block definition.
|
||||
# @param name [String] name of the block definition.
|
||||
# @param units [String] units of the block definition.
|
||||
# @param application_id [String, NilClass] application id of the block definition.
|
||||
def initialize(geometry:, base_point:, name:, units:, application_id: nil)
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def initialize(geometry:, name:, units:, always_face_camera:, sketchup_attributes: {},
|
||||
application_id: nil)
|
||||
super(
|
||||
speckle_type: SPECKLE_TYPE,
|
||||
total_children_count: 0,
|
||||
@@ -29,107 +31,224 @@ module SpeckleConnector
|
||||
)
|
||||
self[:units] = units
|
||||
self[:name] = name
|
||||
self[:basePoint] = base_point
|
||||
self['@geometry'] = geometry
|
||||
self[:always_face_camera] = always_face_camera
|
||||
self[:sketchup_attributes] = sketchup_attributes if sketchup_attributes.any?
|
||||
# FIXME: Since geometry sends with @ as detached, block basePlane renders on viewer.
|
||||
self['@@geometry'] = geometry
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
# @param definition [Sketchup::ComponentDefinition] component definition might be belong to group or component
|
||||
# instance
|
||||
# @param units [String] units of the Sketchup model
|
||||
# @param definitions [Hash{String=>BlockDefinition}] all converted {BlockDefinition}s on the converter.
|
||||
def self.from_definition(definition, units, definitions, &convert)
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
# rubocop:disable Metrics/PerceivedComplexity
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def self.from_definition(definition, units, definitions, preferences, speckle_state, parent, &convert)
|
||||
guid = definition.guid
|
||||
return definitions[guid] if definitions.key?(guid)
|
||||
|
||||
dictionaries = {}
|
||||
if preferences[:model][:include_entity_attributes]
|
||||
if definition.group?
|
||||
if preferences[:model][:include_group_entity_attributes]
|
||||
dictionaries = SketchupModel::Dictionary::DictionaryHandler
|
||||
.attribute_dictionaries_to_speckle(definition)
|
||||
end
|
||||
elsif preferences[:model][:include_component_entity_attributes]
|
||||
dictionaries = SketchupModel::Dictionary::DictionaryHandler.attribute_dictionaries_to_speckle(definition)
|
||||
end
|
||||
end
|
||||
att = dictionaries.any? ? { dictionaries: dictionaries } : {}
|
||||
|
||||
# TODO: Solve logic
|
||||
geometry = if definition.entities[0].is_a?(Sketchup::Edge) || definition.entities[0].is_a?(Sketchup::Face)
|
||||
group_entities_to_speckle(definition, units, definitions, &convert)
|
||||
new_speckle_state, geo = group_entities_to_speckle(
|
||||
definition, preferences, speckle_state, parent, &convert
|
||||
)
|
||||
speckle_state = new_speckle_state
|
||||
geo
|
||||
else
|
||||
definition.entities.map { |entity| convert.call(entity) }
|
||||
definition.entities.map do |entity|
|
||||
next if entity.is_a?(Sketchup::Edge) && entity.faces.any?
|
||||
|
||||
new_speckle_state, converted = convert.call(entity, preferences,
|
||||
speckle_state,
|
||||
definition.persistent_id)
|
||||
speckle_state = new_speckle_state
|
||||
converted
|
||||
end
|
||||
end
|
||||
|
||||
# FIXME: Decide how to approach base point of the definition instead origin.
|
||||
BlockDefinition.new(
|
||||
block_definition = BlockDefinition.new(
|
||||
units: units,
|
||||
name: definition.name,
|
||||
base_point: Geometry::Point.new(0, 0, 0, units),
|
||||
geometry: geometry,
|
||||
application_id: guid
|
||||
always_face_camera: definition.behavior.always_face_camera?,
|
||||
sketchup_attributes: att,
|
||||
application_id: definition.persistent_id.to_s
|
||||
)
|
||||
end
|
||||
|
||||
# Finds or creates a component definition from the geometry and the given name
|
||||
# @param sketchup_model [Sketchup::Model] sketchup model to check block definitions.
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
# rubocop:disable Metrics/PerceivedComplexity
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def self.to_native(sketchup_model, geometry, layer, name, application_id = '', &convert)
|
||||
definition = sketchup_model.definitions[name]
|
||||
return definition if definition && (definition.name == name || definition.guid == application_id)
|
||||
|
||||
definition&.entities&.clear!
|
||||
definition ||= sketchup_model.definitions.add(name)
|
||||
definition.layer = layer
|
||||
geometry.each { |obj| convert.call(obj, layer, definition.entities) } if geometry.is_a?(Array)
|
||||
convert.call(geometry, layer, definition.entities) if geometry.is_a?(Hash) && !geometry['speckle_type'].nil?
|
||||
# puts("definition finished: #{name} (#{application_id})")
|
||||
# puts(" entity count: #{definition.entities.count}")
|
||||
definition
|
||||
return speckle_state, block_definition
|
||||
end
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
# rubocop:enable Metrics/PerceivedComplexity
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
def self.group_entities_to_speckle(definition, units, definitions, &convert)
|
||||
def self.get_definition_name(def_obj)
|
||||
return def_obj['name'] unless def_obj['name'].nil?
|
||||
|
||||
return "def::#{def_obj['applicationId']}"
|
||||
end
|
||||
|
||||
# Finds or creates a component definition from the geometry and the given name
|
||||
# @param state [States::State] state of the application.
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
# rubocop:disable Metrics/PerceivedComplexity
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def self.to_native(state, definition_obj, layer, _entities, &convert_to_native)
|
||||
sketchup_model = state.sketchup_state.sketchup_model
|
||||
|
||||
# FIXME: Check later this is a valid check or not. Maybe unnecessary? If necessary document it!
|
||||
# Check definitions from sketchup_model with name and application id
|
||||
definition_name = get_definition_name(definition_obj)
|
||||
application_id = definition_obj['applicationId']
|
||||
definition = sketchup_model.definitions[definition_name]
|
||||
if definition && (definition.name == definition_name || definition.guid == application_id)
|
||||
return state, [definition]
|
||||
end
|
||||
|
||||
geometry = definition_obj['geometry'] || definition_obj['@geometry'] || definition_obj['displayValue']
|
||||
|
||||
always_face_camera = definition_obj['always_face_camera'].nil? ? false : definition_obj['always_face_camera']
|
||||
sketchup_attributes = definition_obj['sketchup_attributes']
|
||||
definition&.entities&.clear!
|
||||
definition ||= sketchup_model.definitions.add(definition_name)
|
||||
definition.layer = layer
|
||||
|
||||
if geometry.is_a?(Array)
|
||||
geometry.each do |obj|
|
||||
state = convert_to_native.call(state, obj, layer, definition.entities)
|
||||
end
|
||||
end
|
||||
if geometry.is_a?(Hash) && !definition_obj['speckle_type'].nil?
|
||||
state = convert_to_native.call(state, geometry, layer, definition.entities)
|
||||
end
|
||||
# puts("definition finished: #{name} (#{application_id})")
|
||||
# puts(" entity count: #{definition.entities.count}")
|
||||
definition.behavior.always_face_camera = always_face_camera
|
||||
unless sketchup_attributes.nil?
|
||||
SketchupModel::Dictionary::DictionaryHandler
|
||||
.attribute_dictionaries_to_native(definition, sketchup_attributes['dictionaries'])
|
||||
end
|
||||
return state, [definition]
|
||||
end
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
# rubocop:enable Metrics/PerceivedComplexity
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
# rubocop:disable Metrics/PerceivedComplexity
|
||||
def self.group_entities_to_speckle(definition, preferences, speckle_state, parent, &convert)
|
||||
orphan_edges = definition.entities.grep(Sketchup::Edge).filter { |edge| edge.faces.none? }
|
||||
lines = orphan_edges.collect do |orphan_edge|
|
||||
Geometry::Line.from_edge(orphan_edge, units)
|
||||
new_speckle_state, converted = convert.call(orphan_edge, preferences, speckle_state, parent)
|
||||
speckle_state = new_speckle_state
|
||||
converted
|
||||
end
|
||||
|
||||
nested_blocks = definition.entities.grep(Sketchup::ComponentInstance).collect do |component_instance|
|
||||
BlockInstance.from_component_instance(component_instance, units, definitions, &convert)
|
||||
new_speckle_state, converted = convert.call(component_instance, preferences, speckle_state, parent)
|
||||
speckle_state = new_speckle_state
|
||||
converted
|
||||
end
|
||||
|
||||
meshes = definition.entities.grep(Sketchup::Face).collect do |face|
|
||||
Geometry::Mesh.from_face(face, units)
|
||||
nested_groups = definition.entities.grep(Sketchup::Group).collect do |group|
|
||||
new_speckle_state, converted = convert.call(group, preferences, speckle_state, parent)
|
||||
speckle_state = new_speckle_state
|
||||
converted
|
||||
end
|
||||
|
||||
lines + nested_blocks + meshes
|
||||
end
|
||||
if preferences[:model][:combine_faces_by_material]
|
||||
mesh_groups = {}
|
||||
definition.entities.grep(Sketchup::Face).collect do |face|
|
||||
new_speckle_state = group_meshes_by_material(
|
||||
face, mesh_groups, speckle_state, preferences, parent, &convert
|
||||
)
|
||||
speckle_state = new_speckle_state
|
||||
end
|
||||
# Update mesh overwrites points and polygons into base object.
|
||||
mesh_groups.each { |_, mesh| mesh.first.update_mesh }
|
||||
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def self.group_meshes_by_material(definition, face, mat_groups, units)
|
||||
# convert material
|
||||
mat_id = face.material.nil? ? 'none' : face.material.entityID
|
||||
mat_groups[mat_id] = initialise_group_mesh(face, definition.bounds, units) unless mat_groups.key?(mat_id)
|
||||
mat_group = mat_groups[mat_id]
|
||||
if face.loops.size > 1
|
||||
mesh = face.mesh
|
||||
mat_group[:'@(31250)vertices'].push(*Geometry::Mesh.mesh_points_to_array(mesh, units))
|
||||
mat_group[:'@(62500)faces'].push(*Geometry::Mesh.mesh_faces_to_array(mesh, mat_group[:pt_count] - 1))
|
||||
mat_group[:'@(31250)faceEdgeFlags'].push(*Geometry::Mesh.mesh_edge_flags_to_array(mesh))
|
||||
return speckle_state, lines + nested_blocks + nested_groups + mesh_groups.values
|
||||
else
|
||||
mat_group[:'@(31250)vertices'].push(*Geometry::Mesh.face_vertices_to_array(face, units))
|
||||
mat_group[:'@(62500)faces'].push(*Geometry::Mesh.face_indices_to_array(face, mat_group[:pt_count]))
|
||||
mat_group[:'@(31250)faceEdgeFlags'].push(*Geometry::Mesh.face_edge_flags_to_array(face))
|
||||
meshes = []
|
||||
definition.entities.grep(Sketchup::Face).collect do |face|
|
||||
new_speckle_state, converted = convert.call(face, preferences, speckle_state, parent)
|
||||
meshes.append(converted)
|
||||
speckle_state = new_speckle_state
|
||||
end
|
||||
|
||||
return speckle_state, lines + nested_blocks + nested_groups + meshes
|
||||
end
|
||||
mat_group[:pt_count] += face.vertices.count
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
# rubocop:enable Metrics/PerceivedComplexity
|
||||
|
||||
def self.initialise_group_mesh(face, bounds, units)
|
||||
has_any_soften_edge = face.edges.any?(&:soft?)
|
||||
mesh = Geometry::Mesh.new(
|
||||
units: units,
|
||||
render_material: face.material.nil? ? nil : RenderMaterial.from_material(face.material),
|
||||
bbox: Geometry::BoundingBox.from_bounds(bounds, units),
|
||||
vertices: [],
|
||||
faces: [],
|
||||
face_edge_flags: [],
|
||||
sketchup_attributes: { is_soften: has_any_soften_edge }
|
||||
)
|
||||
mesh[:pt_count] = 0
|
||||
mesh
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def self.group_meshes_by_material(face, mesh_groups, speckle_state, preferences, parent, &convert)
|
||||
# convert material
|
||||
mesh_group_id = get_mesh_group_id(face, preferences[:model])
|
||||
new_speckle_state, converted = convert.call(face, preferences, speckle_state, parent)
|
||||
mesh_groups[mesh_group_id] = converted unless mesh_groups.key?(mesh_group_id)
|
||||
mesh_group = mesh_groups[mesh_group_id]
|
||||
mesh_group[0].face_to_mesh(face)
|
||||
mesh_group[1].append(face)
|
||||
new_speckle_state
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
# Mesh group id helps to determine how to group faces into meshes.
|
||||
# @param face [Sketchup::Face] face to get mesh group id.
|
||||
def self.get_mesh_group_id(face, model_preferences)
|
||||
if model_preferences[:include_entity_attributes] &&
|
||||
model_preferences[:include_face_entity_attributes] &&
|
||||
attribute_dictionary?(face)
|
||||
return face.persistent_id.to_s
|
||||
end
|
||||
|
||||
material = face.material || face.back_material
|
||||
return 'none' if material.nil?
|
||||
|
||||
return material.entityID.to_s
|
||||
end
|
||||
|
||||
def self.attribute_dictionary?(face)
|
||||
any_attribute_dictionary = !(face.attribute_dictionaries.nil? || face.attribute_dictionaries.first.nil?)
|
||||
return any_attribute_dictionary unless any_attribute_dictionary
|
||||
|
||||
# If there are any attribute dictionary, then make sure that they are not ignored ones.
|
||||
all_attribute_dictionary_ignored = face.attribute_dictionaries.all? do |dict|
|
||||
ignored_dictionaries.include?(dict.name)
|
||||
end
|
||||
!all_attribute_dictionary_ignored
|
||||
end
|
||||
|
||||
def self.ignored_dictionaries
|
||||
[
|
||||
'Speckle_Base_Object'
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -5,6 +5,7 @@ require_relative 'transform'
|
||||
require_relative 'block_definition'
|
||||
require_relative '../base'
|
||||
require_relative '../geometry/bounding_box'
|
||||
require_relative '../../sketchup_model/dictionary/dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleObjects
|
||||
@@ -16,14 +17,13 @@ module SpeckleConnector
|
||||
# @param units [String] units of the block instance.
|
||||
# @param is_sketchup_group [Boolean] whether is sketchup group or not. Sketchup Groups represented as
|
||||
# block instance on Speckle.
|
||||
# @param bbox [Geometry::BoundingBox] bounding box of the block instance.
|
||||
# @param name [String] name of the block instance.
|
||||
# @param transform [Other::Transform] transform of the block instance.
|
||||
# @param block_definition [Other::BlockDefinition] definition of the block instance.
|
||||
# @param sketchup_attributes [Other::BlockDefinition] sketchup attributes of the block instance.
|
||||
# @param sketchup_attributes [Hash{Symbol=>Object}] sketchup attributes of the block instance.
|
||||
# @param application_id [String] application id of the block instance.
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def initialize(units:, is_sketchup_group:, bbox:, name:, render_material:, transform:, block_definition:,
|
||||
def initialize(units:, is_sketchup_group:, name:, render_material:, transform:, block_definition:,
|
||||
sketchup_attributes: {}, application_id: nil)
|
||||
super(
|
||||
speckle_type: SPECKLE_TYPE,
|
||||
@@ -34,35 +34,60 @@ module SpeckleConnector
|
||||
self[:units] = units
|
||||
self[:name] = name
|
||||
self[:is_sketchup_group] = is_sketchup_group
|
||||
self[:bbox] = bbox
|
||||
self[:renderMaterial] = render_material
|
||||
self[:transform] = transform
|
||||
self[:sketchup_attributes] = sketchup_attributes
|
||||
self['@blockDefinition'] = block_definition
|
||||
self[:sketchup_attributes] = sketchup_attributes if sketchup_attributes.any?
|
||||
# FIXME: Since blockDefinition sends with @ as detached, block basePlane renders on viewer.
|
||||
self['@@definition'] = block_definition
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
# @param group [Sketchup::Group] group to convert Speckle BlockInstance
|
||||
def self.from_group(group, units, component_defs, &convert)
|
||||
BlockInstance.new(
|
||||
def self.from_group(group, units, preferences, speckle_state, &convert)
|
||||
new_speckle_state, block_definition = convert.call(group.definition, preferences, speckle_state,
|
||||
group.persistent_id)
|
||||
speckle_state = new_speckle_state
|
||||
dictionaries = {}
|
||||
if preferences[:model][:include_entity_attributes] && preferences[:model][:include_group_entity_attributes]
|
||||
dictionaries = SketchupModel::Dictionary::DictionaryHandler.attribute_dictionaries_to_speckle(group)
|
||||
end
|
||||
att = dictionaries.any? ? { dictionaries: dictionaries } : {}
|
||||
|
||||
block_instance = BlockInstance.new(
|
||||
units: units,
|
||||
application_id: group.guid,
|
||||
is_sketchup_group: true,
|
||||
bbox: Geometry::BoundingBox.from_bounds(group.bounds, units),
|
||||
name: group.name == '' ? nil : group.name,
|
||||
render_material: group.material.nil? ? nil : RenderMaterial.from_material(group.material),
|
||||
transform: Other::Transform.from_transformation(group.transformation, units),
|
||||
block_definition: BlockDefinition.from_definition(group.definition, units, component_defs, &convert)
|
||||
block_definition: block_definition,
|
||||
sketchup_attributes: att,
|
||||
application_id: group.guid
|
||||
)
|
||||
return speckle_state, block_instance
|
||||
end
|
||||
|
||||
# @param component_instance [Sketchup::ComponentInstance] component instance to convert Speckle BlockInstance
|
||||
def self.from_component_instance(component_instance, units, component_defs, &convert)
|
||||
BlockInstance.new(
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def self.from_component_instance(component_instance, units, preferences, speckle_state, &convert)
|
||||
new_speckle_state, block_definition = convert.call(
|
||||
component_instance.definition,
|
||||
preferences,
|
||||
speckle_state,
|
||||
component_instance.persistent_id
|
||||
)
|
||||
speckle_state = new_speckle_state
|
||||
|
||||
dictionaries = {}
|
||||
if preferences[:model][:include_entity_attributes] &&
|
||||
preferences[:model][:include_component_entity_attributes]
|
||||
dictionaries = SketchupModel::Dictionary::DictionaryHandler
|
||||
.attribute_dictionaries_to_speckle(component_instance)
|
||||
end
|
||||
att = dictionaries.any? ? { dictionaries: dictionaries } : {}
|
||||
|
||||
block_instance = BlockInstance.new(
|
||||
units: units,
|
||||
application_id: component_instance.guid,
|
||||
is_sketchup_group: false,
|
||||
bbox: Geometry::BoundingBox.from_bounds(component_instance.bounds, units),
|
||||
name: component_instance.name == '' ? nil : component_instance.name,
|
||||
render_material: if component_instance.material.nil?
|
||||
nil
|
||||
@@ -70,69 +95,117 @@ module SpeckleConnector
|
||||
RenderMaterial.from_material(component_instance.material)
|
||||
end,
|
||||
transform: Other::Transform.from_transformation(component_instance.transformation, units),
|
||||
block_definition: BlockDefinition.from_definition(component_instance.definition, units, component_defs,
|
||||
&convert)
|
||||
block_definition: block_definition,
|
||||
sketchup_attributes: att,
|
||||
application_id: component_instance.persistent_id.to_s
|
||||
)
|
||||
return speckle_state, block_instance
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
# Creates a component instance from a block
|
||||
# @param sketchup_model [Sketchup::Model] sketchup model to check block definitions.
|
||||
# @param state [States::State] state of the application.
|
||||
# @param block [Object] block object that represents Speckle block.
|
||||
# @param layer [Sketchup::Layer] layer to add {Sketchup::Edge} into it.
|
||||
# @param entities [Sketchup::Entities] entities collection to add {Sketchup::Edge} into it.
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def self.to_native(sketchup_model, block, layer, entities, &convert)
|
||||
def self.to_native(state, block, layer, entities, &convert_to_native)
|
||||
# is_group = block.key?("is_sketchup_group") && block["is_sketchup_group"]
|
||||
# something about this conversion is freaking out if nested block geo is a group
|
||||
# so this is set to false always until I can figure this out
|
||||
is_group = false
|
||||
definition = BlockDefinition.to_native(
|
||||
sketchup_model,
|
||||
block['@blockDefinition']['@geometry'],
|
||||
# is_group = block['is_sketchup_group']
|
||||
# NOTE: nil checks for backward compatibility
|
||||
block_definition = block['definition'] || block['blockDefinition'] || block['@blockDefinition']
|
||||
|
||||
state, _definitions = BlockDefinition.to_native(
|
||||
state,
|
||||
block_definition,
|
||||
layer,
|
||||
block['@blockDefinition']['name'],
|
||||
block['@blockDefinition']['applicationId'],
|
||||
&convert
|
||||
entities,
|
||||
&convert_to_native
|
||||
)
|
||||
|
||||
instance_name = block['name'].nil? || block['name'].empty? ? block['id'] : block['name']
|
||||
t_arr = block['transform'].is_a?(Hash) ? block['transform']['value'] : block['transform']
|
||||
transform = Other::Transform.to_native(t_arr, block['units'])
|
||||
instance =
|
||||
if is_group
|
||||
# rubocop:disable SketchupSuggestions/AddGroup
|
||||
group = entities.add_group(definition.entities.to_a)
|
||||
group.layer = layer
|
||||
# rubocop:enable SketchupSuggestions/AddGroup
|
||||
else
|
||||
instance = entities.add_instance(definition, transform)
|
||||
instance.layer = layer
|
||||
instance
|
||||
end
|
||||
# erase existing instances after creation and before rename because you can't have definitions
|
||||
# without instances
|
||||
find_and_erase_existing_instance(definition, instance_name, block['applicationId'])
|
||||
puts("Failed to create instance for speckle block instance #{block['id']}") if instance.nil?
|
||||
instance.transformation = transform if is_group
|
||||
instance.material = Other::RenderMaterial.to_native(sketchup_model, block['renderMaterial'])
|
||||
instance.name = instance_name
|
||||
instance
|
||||
definition = state.sketchup_state.sketchup_model
|
||||
.definitions[BlockDefinition.get_definition_name(block_definition)]
|
||||
|
||||
return add_instance_from_definition(state, block, layer, entities, definition, is_group, &convert_to_native)
|
||||
end
|
||||
|
||||
def self.get_transform_matrix(block)
|
||||
if block['transform'].is_a?(Hash)
|
||||
block['transform']['matrix'] || block['transform']['value']
|
||||
else
|
||||
block['transform']
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
# takes a component definition and finds and erases the first instance with the matching name
|
||||
# (and optionally the applicationId)
|
||||
def self.find_and_erase_existing_instance(definition, name, app_id = '')
|
||||
definition.instances.find { |ins| ins.name == name || ins.guid == app_id }&.erase!
|
||||
end
|
||||
# rubocop:disable Metrics/PerceivedComplexity
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
def self.find_and_erase_existing_instance(definition, upcoming_speckle_id, upcoming_app_id = '')
|
||||
definition.instances.find do |ins|
|
||||
next if ins.attribute_dictionaries.nil?
|
||||
next if ins.attribute_dictionaries.to_a.empty?
|
||||
next if ins.attribute_dictionaries.to_a.none? { |dict| dict.name == SPECKLE_BASE_OBJECT }
|
||||
|
||||
private
|
||||
|
||||
def attribute_types
|
||||
ATTRIBUTE_TYPES
|
||||
dict = ins.attribute_dictionaries.to_a.find { |d| d.name == SPECKLE_BASE_OBJECT }
|
||||
speckle_id = dict[:speckle_id]
|
||||
application_id = dict[:application_id]
|
||||
speckle_id == upcoming_speckle_id || application_id == upcoming_app_id
|
||||
end&.erase!
|
||||
end
|
||||
# rubocop:enable Metrics/PerceivedComplexity
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
# rubocop:disable Metrics/PerceivedComplexity
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def self.add_instance_from_definition(state, block, layer, entities, definition, is_group, &convert_to_native)
|
||||
t_arr = get_transform_matrix(block)
|
||||
transform = Other::Transform.to_native(t_arr, block['units'])
|
||||
instance = if is_group
|
||||
# rubocop:disable SketchupSuggestions/AddGroup
|
||||
group = entities.add_group(definition.entities.to_a)
|
||||
group.layer = layer
|
||||
group
|
||||
# rubocop:enable SketchupSuggestions/AddGroup
|
||||
else
|
||||
instance = entities.add_instance(definition, transform)
|
||||
instance.layer = layer
|
||||
instance
|
||||
end
|
||||
|
||||
# erase existing instances after creation and before rename because you can't have definitions
|
||||
# without instances
|
||||
find_and_erase_existing_instance(definition, block['id'], block['applicationId'])
|
||||
puts("Failed to create instance for speckle block instance #{block['id']}") if instance.nil?
|
||||
# Transform already applied to instance unless is group
|
||||
instance.transformation = transform if is_group
|
||||
state, _materials = Other::RenderMaterial.to_native(state, block['renderMaterial'],
|
||||
layer, entities, &convert_to_native)
|
||||
|
||||
# Retrieve material from state
|
||||
unless block['renderMaterial'].nil?
|
||||
material_name = block['renderMaterial']['name'] || block['renderMaterial']['id']
|
||||
material = state.sketchup_state.materials.by_id(material_name)
|
||||
instance.material = material
|
||||
end
|
||||
|
||||
instance.name = block['name'] unless block['name'].nil?
|
||||
unless block['sketchup_attributes'].nil?
|
||||
SketchupModel::Dictionary::DictionaryHandler
|
||||
.attribute_dictionaries_to_native(instance, block['sketchup_attributes']['dictionaries'])
|
||||
end
|
||||
return state, [instance, definition]
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
# rubocop:enable Metrics/PerceivedComplexity
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleObjects
|
||||
module Others
|
||||
# Color object transformations between sketchup and speckle.
|
||||
class Color
|
||||
# @param color [Sketchup::Color] color to convert speckle object
|
||||
def self.to_speckle(color)
|
||||
{
|
||||
red: color.red,
|
||||
green: color.green,
|
||||
blue: color.blue,
|
||||
alpha: color.alpha
|
||||
}
|
||||
end
|
||||
|
||||
def self.to_native(speckle_color)
|
||||
Sketchup::Color.new(speckle_color['red'], speckle_color['green'],
|
||||
speckle_color['blue'], speckle_color['alpha'])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,85 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'render_material'
|
||||
require_relative 'transform'
|
||||
require_relative 'block_definition'
|
||||
require_relative '../../immutable/immutable'
|
||||
require_relative '../../ext/immutable_ruby/hash'
|
||||
require_relative '../base'
|
||||
require_relative '../geometry/bounding_box'
|
||||
require_relative '../../sketchup_model/dictionary/dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleObjects
|
||||
module Other
|
||||
# DisplayValue object definition for Speckle that represents as BlockInstance in Sketchup.
|
||||
class DisplayValue
|
||||
def self.get_definition_name(def_obj)
|
||||
family = def_obj['family']
|
||||
type = def_obj['type']
|
||||
category = def_obj['category']
|
||||
element_id = def_obj['elementId']
|
||||
|
||||
return format_naming_convention([family, type, category, element_id]) unless element_id.nil?
|
||||
|
||||
return "def::#{def_obj['applicationId']}"
|
||||
end
|
||||
|
||||
def self.format_naming_convention(entries)
|
||||
name = ''
|
||||
entries.each_with_index do |entry, index|
|
||||
next if entry.nil?
|
||||
|
||||
name += if index == entries.length - 1
|
||||
entry.to_s
|
||||
else
|
||||
"#{entry}-"
|
||||
end
|
||||
end
|
||||
name
|
||||
end
|
||||
|
||||
# Creates a component definition and instance from a speckle object with a display value
|
||||
# @param state [States::State] state of the application.
|
||||
def self.to_native(state, obj, layer, entities, &convert_to_native)
|
||||
# Switch displayValue with geometry
|
||||
obj = collect_definition_geometries(obj)
|
||||
obj['name'] = get_definition_name(obj)
|
||||
|
||||
state, _definitions = BlockDefinition.to_native(
|
||||
state,
|
||||
obj,
|
||||
layer,
|
||||
entities,
|
||||
&convert_to_native
|
||||
)
|
||||
|
||||
definition = state.sketchup_state.sketchup_model.definitions[BlockDefinition.get_definition_name(obj)]
|
||||
|
||||
BlockInstance.find_and_erase_existing_instance(definition, obj['id'], obj['applicationId'])
|
||||
t_arr = obj['transform']
|
||||
transform = t_arr.nil? ? Geom::Transformation.new : OTHER::Transform.to_native(t_arr, units)
|
||||
instance = entities.add_instance(definition, transform)
|
||||
instance.name = obj['name'] unless obj['name'].nil?
|
||||
return state, [instance, definition]
|
||||
end
|
||||
|
||||
def self.collect_definition_geometries(obj)
|
||||
obj['geometry'] = obj['displayValue']
|
||||
|
||||
if !obj['elements'].nil? && obj['elements'].is_a?(Array)
|
||||
obj['elements'].each do |element|
|
||||
# Mullions is a special case here, they are extracted as base object with @displayValue from revit..
|
||||
if element['@displayValue'].nil?
|
||||
obj['geometry'].append(element)
|
||||
else
|
||||
obj['geometry'] += element['@displayValue']
|
||||
end
|
||||
end
|
||||
end
|
||||
obj
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,7 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../base'
|
||||
require_relative '../../typescript/typescript_object'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleObjects
|
||||
@@ -50,21 +49,25 @@ module SpeckleConnector
|
||||
)
|
||||
end
|
||||
|
||||
# @param sketchup_model [Sketchup::Model] active model on the sketchup.
|
||||
def self.to_native(sketchup_model, render_material)
|
||||
return if render_material.nil?
|
||||
# @param state [States::State] state of the application.
|
||||
def self.to_native(state, render_material, _layer, _entities, &_convert_to_native)
|
||||
return state, [] if render_material.nil?
|
||||
|
||||
sketchup_model = state.sketchup_state.sketchup_model
|
||||
materials = state.sketchup_state.materials
|
||||
|
||||
# return material with same name if it exists
|
||||
name = render_material['name'] || render_material['id']
|
||||
material = sketchup_model.materials[name]
|
||||
return material if material
|
||||
material = materials.by_id(name)
|
||||
return state, [material] if material
|
||||
|
||||
# create a new sketchup material
|
||||
material = sketchup_model.materials.add(name)
|
||||
material.alpha = render_material['opacity']
|
||||
argb = render_material['diffuse']
|
||||
material.color = Sketchup::Color.new((argb >> 16) & 255, (argb >> 8) & 255, argb & 255, (argb >> 24) & 255)
|
||||
material
|
||||
new_sketchup_state = state.sketchup_state.with_materials(materials.add_material(name, material))
|
||||
return state.with_sketchup_state(new_sketchup_state), [material]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'color'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleObjects
|
||||
module Others
|
||||
# Rendering options for scenes.
|
||||
class RenderingOptions
|
||||
# @param options [Sketchup::RenderingOptions] rendering options to convert speckle object
|
||||
def self.to_speckle(options)
|
||||
options.to_h.collect do |option_prop, option_value|
|
||||
speckle_value = if option_value.is_a?(Sketchup::Color)
|
||||
Color.to_speckle(option_value)
|
||||
else
|
||||
option_value
|
||||
end
|
||||
[option_prop.to_sym, speckle_value]
|
||||
end.to_h
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../block_definition'
|
||||
require_relative '../../base'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleObjects
|
||||
module Other
|
||||
module Revit
|
||||
# RevitDefinition for Speckle.
|
||||
class RevitDefinition < Base
|
||||
SPECKLE_TYPE = OBJECTS_OTHER_REVIT_REVITINSTANCE
|
||||
|
||||
def self.get_definition_name(def_obj)
|
||||
family = def_obj['family']
|
||||
type = def_obj['type']
|
||||
category = def_obj['category']
|
||||
|
||||
return "#{family}-#{type}-#{category}-#{def_obj['id']}"
|
||||
end
|
||||
|
||||
def self.to_native(state, definition, layer, entities, &convert_to_native)
|
||||
definition_name = get_definition_name(definition)
|
||||
definition['name'] = definition_name
|
||||
definition['displayValue'] += definition['elements'] unless definition['elements'].nil?
|
||||
BlockDefinition.to_native(state, definition, layer, entities, &convert_to_native)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,45 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'revit_definition'
|
||||
require_relative '../render_material'
|
||||
require_relative '../transform'
|
||||
require_relative '../block_definition'
|
||||
require_relative '../../base'
|
||||
require_relative '../../geometry/bounding_box'
|
||||
require_relative '../../../sketchup_model/dictionary/dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleObjects
|
||||
module Other
|
||||
module Revit
|
||||
# RevitInstance object definition for Speckle.
|
||||
class RevitInstance < Base
|
||||
SPECKLE_TYPE = OBJECTS_OTHER_REVIT_REVITINSTANCE
|
||||
|
||||
# Creates a component instance from a block
|
||||
# @param state [States::State] state of the application.
|
||||
# @param block [Object] block object that represents Speckle block.
|
||||
# @param layer [Sketchup::Layer] layer to add {Sketchup::Edge} into it.
|
||||
# @param entities [Sketchup::Entities] entities collection to add {Sketchup::Edge} into it.
|
||||
def self.to_native(state, block, layer, entities, &convert_to_native)
|
||||
block_definition = block['definition']
|
||||
|
||||
state, _definitions = RevitDefinition.to_native(
|
||||
state,
|
||||
block_definition,
|
||||
layer,
|
||||
entities,
|
||||
&convert_to_native
|
||||
)
|
||||
|
||||
definition = state.sketchup_state.sketchup_model
|
||||
.definitions[RevitDefinition.get_definition_name(block_definition)]
|
||||
|
||||
return BlockInstance.add_instance_from_definition(state, block, layer, entities,
|
||||
definition, false, &convert_to_native)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -11,8 +11,7 @@ module SpeckleConnector
|
||||
|
||||
# @param units [String] units of the transform.
|
||||
# @param value [Array<Numeric>] values of the transform.
|
||||
# @param sketchup_attributes [Other::BlockDefinition] sketchup attributes of the transform.
|
||||
def initialize(units:, value:, sketchup_attributes: {})
|
||||
def initialize(units:, value:)
|
||||
super(
|
||||
speckle_type: SPECKLE_TYPE,
|
||||
total_children_count: 0,
|
||||
@@ -21,7 +20,6 @@ module SpeckleConnector
|
||||
)
|
||||
self[:units] = units
|
||||
self[:value] = value
|
||||
self[:sketchup_attributes] = sketchup_attributes
|
||||
end
|
||||
|
||||
def self.from_transformation(transformation, units)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../immutable/immutable'
|
||||
require_relative '../sketchup_model/materials/materials'
|
||||
require_relative '../sketchup_model/definitions/definitions'
|
||||
|
||||
module SpeckleConnector
|
||||
module States
|
||||
@@ -10,9 +12,17 @@ module SpeckleConnector
|
||||
# @return [Sketchup::Model] active model on the sketchup
|
||||
attr_reader :sketchup_model
|
||||
|
||||
# @return [SketchupModel::Materials] materials by their id
|
||||
attr_reader :materials
|
||||
|
||||
# @return [SketchupModel::Definitions] definitions by their guid and name
|
||||
attr_reader :definitions
|
||||
|
||||
# @param sketchup_model [Sketchup::Model] active model on the sketchup
|
||||
def initialize(sketchup_model)
|
||||
@sketchup_model = sketchup_model
|
||||
@materials = SketchupModel::Materials.from_sketchup_model(sketchup_model)
|
||||
@definitions = SketchupModel::Definitions.from_sketchup_model(sketchup_model)
|
||||
end
|
||||
|
||||
# @return [Integer] length units code of the sketchup model.
|
||||
@@ -20,6 +30,10 @@ module SpeckleConnector
|
||||
def length_units
|
||||
sketchup_model.options['UnitsOptions']['LengthUnit']
|
||||
end
|
||||
|
||||
def with_materials(new_materials)
|
||||
with(:@materials => new_materials)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
require_relative '../immutable/immutable'
|
||||
require_relative '../callbacks/callback_message'
|
||||
require_relative '../speckle_entities/speckle_entity'
|
||||
|
||||
module SpeckleConnector
|
||||
module States
|
||||
@@ -9,19 +10,33 @@ module SpeckleConnector
|
||||
class SpeckleState
|
||||
include Immutable::ImmutableUtils
|
||||
|
||||
# @return [Hash] accounts on appdata.
|
||||
# @return [ImmutableHash{Integer=>SpeckleEntities::SpeckleEntity}] persistent_id of the sketchup entity and
|
||||
# corresponding speckle entity
|
||||
attr_reader :speckle_entities
|
||||
|
||||
# @return [Array] accounts on appdata.
|
||||
attr_reader :accounts
|
||||
|
||||
# @return [Hash{Class => Observer}] the observer objects that are used to attach to objects in Sketchup to collect
|
||||
# events that are triggered from Sketchup
|
||||
attr_reader :observers
|
||||
|
||||
# @return [Hash] queue to send to server.
|
||||
attr_reader :message_queue
|
||||
|
||||
# @return [Hash] stream queue to send to server.
|
||||
attr_reader :stream_queue
|
||||
|
||||
def initialize(accounts, queue, stream_queue)
|
||||
# @return [Relations::ManyToOneRelation] relations between objects.
|
||||
attr_accessor :relation
|
||||
|
||||
def initialize(accounts, observers, queue, stream_queue)
|
||||
@accounts = accounts
|
||||
@observers = observers
|
||||
@message_queue = queue
|
||||
@stream_queue = stream_queue
|
||||
@speckle_entities = Immutable::EmptyHash
|
||||
@relation = Relations::ManyToOneRelation.new
|
||||
end
|
||||
|
||||
# @param callback_name [String] name of the callback command
|
||||
@@ -32,6 +47,41 @@ module SpeckleConnector
|
||||
new_queue = message_queue.merge({ "#{callback_name}": next_queue_message })
|
||||
with(:@message_queue => new_queue)
|
||||
end
|
||||
|
||||
def with_invalid_streams_queue
|
||||
new_queue = message_queue.merge({ "updateInvalidStreams":
|
||||
"updateInvalidStreams(#{JSON.generate(invalid_streams)})" })
|
||||
with(:@message_queue => new_queue)
|
||||
end
|
||||
|
||||
def with_empty_invalid_streams_queue
|
||||
new_queue = message_queue.merge({ "updateInvalidStreams":
|
||||
"updateInvalidStreams(#{JSON.generate([])})" })
|
||||
with(:@message_queue => new_queue)
|
||||
end
|
||||
|
||||
def with_accounts(new_accounts)
|
||||
with(:@accounts => new_accounts)
|
||||
end
|
||||
|
||||
def with_speckle_entity(traversed_entity)
|
||||
new_speckle_entities = speckle_entities.put(traversed_entity.application_id, traversed_entity)
|
||||
with_speckle_entities(new_speckle_entities)
|
||||
end
|
||||
|
||||
def with_speckle_entities(new_speckle_entities)
|
||||
with(:@speckle_entities => new_speckle_entities)
|
||||
end
|
||||
|
||||
def with_relation(new_relation)
|
||||
with(:@relation => new_relation)
|
||||
end
|
||||
|
||||
def invalid_streams
|
||||
speckle_entities.collect do |_id, speckle_entity|
|
||||
speckle_entity.invalid_stream_ids
|
||||
end.reduce([], :concat).uniq
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -40,6 +40,18 @@ module SpeckleConnector
|
||||
new_speckle_state = speckle_state.with(:@stream_queue => {})
|
||||
with(:@speckle_state => new_speckle_state)
|
||||
end
|
||||
|
||||
def with_speckle_state(new_speckle_state)
|
||||
with(:@speckle_state => new_speckle_state)
|
||||
end
|
||||
|
||||
def with_sketchup_state(new_sketchup_state)
|
||||
with(:@sketchup_state => new_sketchup_state)
|
||||
end
|
||||
|
||||
def with_user_state(new_user_state)
|
||||
with(:@user_state => new_user_state)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,12 +8,24 @@ module SpeckleConnector
|
||||
class UserState
|
||||
include Immutable::ImmutableUtils
|
||||
|
||||
# @return [Hash{Symbol => Object}] user specific preferences
|
||||
# @return [ImmutableHash{Symbol => Object}] user specific preferences
|
||||
attr_reader :preferences
|
||||
|
||||
def initialize(preferences)
|
||||
@preferences = preferences
|
||||
end
|
||||
|
||||
def user_preferences
|
||||
@preferences[:user]
|
||||
end
|
||||
|
||||
def model_preferences
|
||||
@preferences[:model]
|
||||
end
|
||||
|
||||
def with_preferences(new_preferences)
|
||||
with(:@preferences => new_preferences)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'json'
|
||||
|
||||
module SpeckleConnector
|
||||
module Typescript
|
||||
# Object to help convert object attributes to JSON and by checking types.
|
||||
class TypescriptObject
|
||||
# @param attributes [Hash{Symbol=>Object}] attributes are given as key value pairs
|
||||
def initialize(**attributes)
|
||||
@attributes = attributes
|
||||
check_attributes
|
||||
end
|
||||
|
||||
# @return [String] the JSON representation of the object
|
||||
def to_json(*options)
|
||||
@attributes.to_json(*options)
|
||||
end
|
||||
|
||||
def to_h(*options)
|
||||
JSON.parse(to_json(*options), { symbolize_names: true })
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
def check_attributes
|
||||
attribute_types.each do |key, class_or_classes|
|
||||
value = @attributes[key]
|
||||
case class_or_classes
|
||||
when Array
|
||||
is_class_correct = class_or_classes.any? do |klass|
|
||||
raise "#{klass} is not a class" unless klass.is_a? Class
|
||||
|
||||
value.is_a? klass
|
||||
end
|
||||
raise "attribute #{key} is of class #{value.class} and not #{class_or_classes}" unless is_class_correct
|
||||
when Class
|
||||
raise ArgumentError, "#{class_or_classes} should be class" unless class_or_classes.is_a? Class
|
||||
|
||||
unless value.is_a? class_or_classes
|
||||
raise ArgumentError,
|
||||
"attribute #{key} is of class #{value.class} and not #{class_or_classes}"
|
||||
end
|
||||
else
|
||||
raise ArgumentError, "#{class_or_classes} should be class or array of classes"
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
|
||||
def attribute_types
|
||||
raise NotImplementedError, 'Implement in child class'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -29,11 +29,12 @@ module SpeckleConnector
|
||||
|
||||
# Show dialog if it's not visible yet
|
||||
def show
|
||||
bring_to_front if html_dialog.visible?
|
||||
return if html_dialog.visible?
|
||||
|
||||
# reset dialog only if it is marked ready, otherwise
|
||||
# add_exec_callback is triggered twice upon first initialization
|
||||
reset_dialog! if @ready
|
||||
reset_dialog!
|
||||
html_dialog.show
|
||||
end
|
||||
|
||||
@@ -73,6 +74,9 @@ module SpeckleConnector
|
||||
# @return [UI::HtmlDialog] the Sketchup interface to html dialog
|
||||
def init_dialog
|
||||
dialog = UI::HtmlDialog.new(@dialog_specs)
|
||||
dialog.set_can_close do
|
||||
true
|
||||
end
|
||||
File.exist?(@htm_file) ? dialog.set_file(@htm_file) : dialog.set_url('http://localhost:8081')
|
||||
# dialog.set_url('http://localhost:8081') # uncomment this line if you want to use your local version of ui
|
||||
add_exec_callback(dialog)
|
||||
@@ -93,7 +97,7 @@ module SpeckleConnector
|
||||
commands = CommandParser.parse_commands(data)
|
||||
commands.each do |cmd|
|
||||
puts '### COMMAND CALLED BY DIALOG ###'
|
||||
puts "name: #{cmd.name}, data: #{cmd.data}"
|
||||
puts "name: #{cmd.name}"
|
||||
@ready = true if cmd.name == DIALOG_READY
|
||||
@commands[cmd.name].run(cmd.data)
|
||||
end
|
||||
|
||||
@@ -11,10 +11,16 @@ require_relative '../commands/dialog_ready'
|
||||
require_relative '../commands/save_stream'
|
||||
require_relative '../commands/remove_stream'
|
||||
require_relative '../commands/notify_connected'
|
||||
require_relative '../commands/user_preferences_updated'
|
||||
require_relative '../commands/model_preferences_updated'
|
||||
require_relative '../commands/activate_diffing'
|
||||
|
||||
require_relative '../actions/reload_accounts'
|
||||
require_relative '../actions/load_saved_streams'
|
||||
require_relative '../actions/init_local_accounts'
|
||||
require_relative '../actions/collect_preferences'
|
||||
require_relative '../actions/deactivate_diffing'
|
||||
require_relative '../actions/collect_versions'
|
||||
|
||||
module SpeckleConnector
|
||||
module Ui
|
||||
@@ -60,7 +66,13 @@ module SpeckleConnector
|
||||
load_saved_streams: Commands::ActionCommand.new(@app, Actions::LoadSavedStreams),
|
||||
save_stream: Commands::SaveStream.new(@app),
|
||||
remove_stream: Commands::RemoveStream.new(@app),
|
||||
notify_connected: Commands::NotifyConnected.new(@app)
|
||||
notify_connected: Commands::NotifyConnected.new(@app),
|
||||
collect_preferences: Commands::ActionCommand.new(@app, Actions::CollectPreferences),
|
||||
collect_versions: Commands::ActionCommand.new(@app, Actions::CollectVersions),
|
||||
user_preferences_updated: Commands::UserPreferencesUpdated.new(@app),
|
||||
model_preferences_updated: Commands::ModelPreferencesUpdated.new(@app),
|
||||
activate_diffing: Commands::ActivateDiffing.new(@app),
|
||||
deactivate_diffing: Commands::ActionCommand.new(@app, Actions::DeactivateDiffing)
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../test_helper'
|
||||
require_relative '../../../speckle_connector/src/states/speckle_state'
|
||||
require_relative '../../../speckle_connector/src/speckle_objects/geometry/point'
|
||||
require_relative '../../../speckle_connector/src/speckle_objects/geometry/line'
|
||||
require_relative '../../../speckle_connector/src/convertors/base_object_serializer'
|
||||
|
||||
module SpeckleConnector
|
||||
module Converters
|
||||
class BaseObjectSerializerTest < Minitest::Test
|
||||
def setup
|
||||
# Do nothing
|
||||
end
|
||||
|
||||
def teardown
|
||||
# Do nothing
|
||||
end
|
||||
|
||||
def test_base_initialize
|
||||
speckle_state = States::SpeckleState.new({}, {}, {}, {})
|
||||
start_point = SpeckleObjects::Geometry::Point.new(0.0, 0.0, 0.0, 'm')
|
||||
end_point = SpeckleObjects::Geometry::Point.new(10.0, 10.0, 0.0, 'm')
|
||||
line = SpeckleObjects::Geometry::Line.test_line(start_point, end_point, 'm')
|
||||
serializer = Converters::BaseObjectSerializer.new
|
||||
|
||||
new_speckle_state, id, traversed = serializer.serialize(line, speckle_state)
|
||||
assert_equal(id, "a53c02860be04238514f1a5b00883fe2")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,103 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../test_helper'
|
||||
require_relative '../../../speckle_connector/src/ext/sqlite3'
|
||||
require_relative '../../../speckle_connector/src/constants/path_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
class Sqlite3Test < Minitest::Test
|
||||
TABLE_NAME = "sketchup_test"
|
||||
TABLE_COLUMNS = "(hash TEXT PRIMARY KEY NOT NULL, content TEXT NOT NULL);"
|
||||
INSERT_DATA = "('oguzhan', 'koral');"
|
||||
|
||||
def setup
|
||||
# Do nothing
|
||||
end
|
||||
|
||||
def teardown
|
||||
# Do nothing
|
||||
end
|
||||
|
||||
def test_table_exists
|
||||
db_path = SPECKLE_TEST_DB_PATH
|
||||
|
||||
db = Sqlite3::Database.new(db_path)
|
||||
exists = db.table_exist?('objects')
|
||||
db.close
|
||||
assert_equal(exists, false)
|
||||
end
|
||||
|
||||
def test_create_table
|
||||
# Init file and close it
|
||||
test_database_file = File.new(SPECKLE_TEST_DB_PATH, "w")
|
||||
test_database_file.close
|
||||
|
||||
# Init sqlite database and create table
|
||||
db = Sqlite3::Database.new(test_database_file.path)
|
||||
db.exec("CREATE TABLE #{TABLE_NAME} #{TABLE_COLUMNS}")
|
||||
exists = db.table_exist?(TABLE_NAME)
|
||||
db.close
|
||||
|
||||
# Assert
|
||||
assert_equal(exists, true)
|
||||
|
||||
# Delete test database file
|
||||
File.delete(test_database_file.path) if File.exist?(test_database_file.path)
|
||||
end
|
||||
|
||||
def test_insert_to_table
|
||||
# Init file and close it
|
||||
test_database_file = File.new(SPECKLE_TEST_DB_PATH, "w")
|
||||
test_database_file.close
|
||||
|
||||
# Init sqlite database
|
||||
db = Sqlite3::Database.new(test_database_file.path)
|
||||
|
||||
# Create table
|
||||
db.exec("CREATE TABLE #{TABLE_NAME} #{TABLE_COLUMNS}")
|
||||
|
||||
# Insert data
|
||||
db.exec("INSERT INTO #{TABLE_NAME} VALUES #{INSERT_DATA}")
|
||||
|
||||
# Select data
|
||||
data = db.exec("SELECT content FROM #{TABLE_NAME} WHERE hash = 'oguzhan'")
|
||||
|
||||
# Close database
|
||||
db.close
|
||||
|
||||
assert_equal(data, [["koral"]])
|
||||
|
||||
# Delete test database file
|
||||
File.delete(test_database_file.path) if File.exist?(test_database_file.path)
|
||||
end
|
||||
|
||||
def test_update_value
|
||||
# Init file and close it
|
||||
test_database_file = File.new(SPECKLE_TEST_DB_PATH, "w")
|
||||
test_database_file.close
|
||||
|
||||
# Init sqlite database
|
||||
db = Sqlite3::Database.new(test_database_file.path)
|
||||
|
||||
# Create table
|
||||
db.exec("CREATE TABLE #{TABLE_NAME} #{TABLE_COLUMNS}")
|
||||
|
||||
# Insert data
|
||||
db.exec("INSERT INTO #{TABLE_NAME} VALUES #{INSERT_DATA}")
|
||||
|
||||
# Update data
|
||||
db.exec("UPDATE #{TABLE_NAME} SET content = 'updated_koral' WHERE hash = 'oguzhan'")
|
||||
|
||||
# Select data
|
||||
data = db.exec("SELECT content FROM #{TABLE_NAME} WHERE hash = 'oguzhan'")
|
||||
|
||||
# Close database
|
||||
db.close
|
||||
|
||||
assert_equal(data, [["updated_koral"]])
|
||||
|
||||
# Delete test database file
|
||||
File.delete(test_database_file.path) if File.exist?(test_database_file.path)
|
||||
end
|
||||
end
|
||||
end
|
||||
+52
-22
@@ -21,12 +21,10 @@
|
||||
solo
|
||||
/>
|
||||
<v-spacer />
|
||||
<v-btn icon small class="mx-1" @click="switchTheme">
|
||||
<v-icon>mdi-theme-light-dark</v-icon>
|
||||
</v-btn>
|
||||
<v-btn icon small class="mx-1" @click="requestRefresh">
|
||||
<v-icon>mdi-refresh</v-icon>
|
||||
</v-btn>
|
||||
<settings-dialog :preferences="preferences"/>
|
||||
<v-menu v-if="loggedIn" bottom min-width="200px" rounded offset-y>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn class="ml-1" icon x-large v-on="on">
|
||||
@@ -58,10 +56,10 @@
|
||||
{{ user.bio ? 'Bio: ' + user.bio : '' }}
|
||||
</div>
|
||||
</v-card-text>
|
||||
<v-card-text v-if="accounts">
|
||||
<v-card-text v-if="accounts()">
|
||||
<v-divider class="my-3" />
|
||||
|
||||
<div v-for="account in accounts" :key="account.id">
|
||||
<div v-for="account in accounts()" :key="account.id">
|
||||
<v-btn
|
||||
v-if="account.userInfo.id != user.id"
|
||||
rounded
|
||||
@@ -82,11 +80,18 @@
|
||||
</v-menu>
|
||||
</v-app-bar>
|
||||
|
||||
<create-stream/>
|
||||
<create-stream-dialog
|
||||
v-if="accounts().length !== 0"
|
||||
:account-id="activeAccount().userInfo.id"
|
||||
:server-url="activeAccount().serverInfo.url"
|
||||
/>
|
||||
|
||||
<v-container fluid>
|
||||
<v-container v-if="accounts().length !== 0" fluid>
|
||||
<router-view :stream-search-query="streamSearchQuery" />
|
||||
</v-container>
|
||||
<v-container v-else>
|
||||
<login/>
|
||||
</v-container>
|
||||
<global-toast />
|
||||
</v-main>
|
||||
</v-app>
|
||||
@@ -97,16 +102,28 @@
|
||||
import { bus } from './main'
|
||||
import userQuery from './graphql/user.gql'
|
||||
import { onLogin } from './vue-apollo'
|
||||
import CreateStream from "@/components/CreateStream";
|
||||
import Login from "@/views/Login";
|
||||
|
||||
global.collectPreferences = function (preferences) {
|
||||
bus.$emit('update-preferences', preferences)
|
||||
}
|
||||
|
||||
global.collectVersions = function (versions) {
|
||||
let vers = JSON.parse(versions)
|
||||
localStorage.setItem('hostAppVersion', vers.sketchup)
|
||||
localStorage.setItem('speckleVersion', vers.speckle)
|
||||
}
|
||||
|
||||
global.loadAccounts = function (accounts) {
|
||||
console.log('>>> SpeckleSketchup: Loading accounts', accounts)
|
||||
localStorage.setItem('localAccounts', JSON.stringify(accounts))
|
||||
let uuid = localStorage.getItem('uuid')
|
||||
if (uuid) {
|
||||
global.setSelectedAccount(accounts.find((acct) => acct['userInfo']['id'] == uuid))
|
||||
} else {
|
||||
global.setSelectedAccount(accounts.find((acct) => acct['isDefault']))
|
||||
if (accounts.length !== 0){
|
||||
if (uuid) {
|
||||
global.setSelectedAccount(accounts.find((acct) => acct['userInfo']['id'] === uuid))
|
||||
} else {
|
||||
global.setSelectedAccount(accounts.find((acct) => acct['isDefault']))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +138,9 @@ global.setSelectedAccount = function (account) {
|
||||
export default {
|
||||
name: 'App',
|
||||
components: {
|
||||
CreateStream: () => import('@/components/CreateStream'),
|
||||
Login,
|
||||
CreateStreamDialog: () => import('@/components/dialogs/CreateStreamDialog'),
|
||||
SettingsDialog: () => import('@/components/dialogs/SettingsDialog'),
|
||||
GlobalToast: () => import('@/components/GlobalToast')
|
||||
},
|
||||
props: {
|
||||
@@ -135,15 +154,13 @@ export default {
|
||||
streamSearchQuery: null,
|
||||
createNewStreamDialog: false,
|
||||
createStreamByIdDialog: false,
|
||||
createStreamByIdText: ""
|
||||
createStreamByIdText: "",
|
||||
preferences: {}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
loggedIn() {
|
||||
return localStorage.getItem('SpeckleSketchup.AuthToken') !== null
|
||||
},
|
||||
accounts() {
|
||||
return JSON.parse(localStorage.getItem('localAccounts'))
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
@@ -163,14 +180,27 @@ export default {
|
||||
if (!this.user) this.$apollo.queries.user.refetch()
|
||||
})
|
||||
|
||||
this.$vuetify.theme.dark = localStorage.getItem('theme') == 'dark'
|
||||
bus.$on('update-preferences', async (preferences) => {
|
||||
let prefs = JSON.parse(preferences)
|
||||
this.preferences = prefs
|
||||
this.$vuetify.theme.dark = this.preferences.user.dark_theme
|
||||
})
|
||||
|
||||
// Collect versions to inform mixpanel
|
||||
sketchup.exec({name: "collect_versions", data: {}})
|
||||
|
||||
// Collect preferences to render UI according to it
|
||||
sketchup.exec({name: "collect_preferences", data: {}})
|
||||
|
||||
// Collect accounts from 'Accounts.db' by ruby sqlite3
|
||||
sketchup.exec({name: "init_local_accounts", data: {}})
|
||||
},
|
||||
methods: {
|
||||
switchTheme() {
|
||||
this.$vuetify.theme.dark = !this.$vuetify.theme.dark
|
||||
localStorage.setItem('theme', this.$vuetify.theme.dark ? 'dark' : 'light')
|
||||
this.$mixpanel.track('Connector Action', { name: 'Toggle Theme' })
|
||||
accounts() {
|
||||
return JSON.parse(localStorage.getItem('localAccounts'))
|
||||
},
|
||||
activeAccount(){
|
||||
return this.accounts().find((account) => account['isDefault'])
|
||||
},
|
||||
switchAccount(account) {
|
||||
this.$mixpanel.track('Connector Action', { name: 'Account Select' })
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<template>
|
||||
<v-snackbar v-model="snack" app bottom color="primary">
|
||||
{{ text }}
|
||||
<v-snackbar v-if="text" v-model="snack" app bottom :color="color">
|
||||
<div v-for="(line, index) in text.split('\n')" :key="index">
|
||||
{{ line }}
|
||||
</div>
|
||||
<template #action="{}">
|
||||
<v-btn v-if="actionName" small outlined @click="openUrl(url)" @click:append="snack = false">
|
||||
{{ actionName }}
|
||||
@@ -16,6 +18,7 @@ export default {
|
||||
data() {
|
||||
return {
|
||||
snack: false,
|
||||
color: "primary",
|
||||
text: null,
|
||||
actionName: null,
|
||||
url: null
|
||||
@@ -33,6 +36,14 @@ export default {
|
||||
mounted() {
|
||||
this.$eventHub.$on('notification', (args) => {
|
||||
this.snack = true
|
||||
this.color = "primary"
|
||||
this.text = args.text
|
||||
this.actionName = args.action ? args.action.name : null
|
||||
this.url = args.action ? args.action.url : null
|
||||
})
|
||||
this.$eventHub.$on('error', (args) => {
|
||||
this.snack = true
|
||||
this.color = "#CC3300"
|
||||
this.text = args.text
|
||||
this.actionName = args.action ? args.action.name : null
|
||||
this.url = args.action ? args.action.url : null
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
@mouseenter="hover = true"
|
||||
@mouseleave="hover = false"
|
||||
>
|
||||
<v-toolbar flat height="70">
|
||||
<v-toolbar flat height="70" :color="getColor(invalid)">
|
||||
<v-btn v-tooptip="''" icon small outlined class="delta-btn" v-if="invalid" @click="activateDiffing">
|
||||
<v-icon v-if="!diffing" class="toggleUpDown" :class='{ "rotate": diffing }' small>mdi-eye-off-outline</v-icon>
|
||||
<v-icon v-else class="toggleUpDown" :class='{ "rotate": diffing }' small>mdi-eye</v-icon>
|
||||
</v-btn>
|
||||
<v-toolbar-title class="ml-0" style="position: relative; left: -10px">
|
||||
<!-- Uncomment when pinning is in place and add style="position: relative; left: -10px" to the element above :) -->
|
||||
<v-btn
|
||||
@@ -55,16 +59,22 @@
|
||||
<timeago class="mr-1" :datetime="stream.updatedAt" />
|
||||
|
|
||||
<v-icon class="ml-1" small>mdi-account-key-outline</v-icon>
|
||||
{{ stream.role.split(':')[1] }}
|
||||
{{ stream.role === null ? "" : stream.role.split(':')[1] }}
|
||||
</v-card-text>
|
||||
<v-card-text class="d-flex align-center pb-5 mb-5 -mt-2" style="height: 50px">
|
||||
<v-menu offset-y>
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-slide-x-transition>
|
||||
<div v-show="hover">
|
||||
<create-branch-dialog :stream-name="stream.name" :stream-id="streamId"/>
|
||||
</div>
|
||||
</v-slide-x-transition>
|
||||
<v-chip v-if="stream.branches" small v-bind="attrs" class="mr-1" v-on="on">
|
||||
<v-icon small class="mr-1 float-left">mdi-source-branch</v-icon>
|
||||
{{ branchName }}
|
||||
</v-chip>
|
||||
</template>
|
||||
<!-- Branch list -->
|
||||
<v-list dense>
|
||||
<v-list-item
|
||||
v-for="(branch, index) in stream.branches.items"
|
||||
@@ -73,7 +83,7 @@
|
||||
@click="switchBranch(branch.name)"
|
||||
>
|
||||
<v-list-item-title class="text-caption font-weight-regular">
|
||||
<v-icon v-if="branch.name == branchName" small class="mr-1 float-left">
|
||||
<v-icon v-if="branch.name === branchName" small class="mr-1 float-left">
|
||||
mdi-check
|
||||
</v-icon>
|
||||
<v-icon v-else small class="mr-1 float-left">mdi-source-branch</v-icon>
|
||||
@@ -151,7 +161,7 @@ import gql from 'graphql-tag'
|
||||
import { bus } from '../main'
|
||||
import streamQuery from '../graphql/stream.gql'
|
||||
import ObjectLoader from '@speckle/objectloader'
|
||||
import { BaseObjectSerializer } from '../utils/serialization'
|
||||
import {HostApplications} from '@/utils/hostApplications'
|
||||
|
||||
global.convertedFromSketchup = function (streamId, batches, commitId, totalChildrenCount) {
|
||||
bus.$emit(`sketchup-objects-${streamId}`, batches, commitId, totalChildrenCount)
|
||||
@@ -171,6 +181,9 @@ global.oneClickSend = function (streamId) {
|
||||
|
||||
export default {
|
||||
name: 'StreamCard',
|
||||
components: {
|
||||
CreateBranchDialog: () => import('@/components/dialogs/CreateBranchDialog'),
|
||||
},
|
||||
props: {
|
||||
streamId: {
|
||||
type: String,
|
||||
@@ -189,7 +202,9 @@ export default {
|
||||
loadingStage: null,
|
||||
branchName: 'main',
|
||||
commitId: 'latest',
|
||||
commitMessage: null
|
||||
commitMessage: null,
|
||||
invalid: false,
|
||||
diffing: false
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
@@ -261,16 +276,39 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
selectedBranch() {
|
||||
if (this.$apollo.loading) return
|
||||
return this.stream.branches.items.find((branch) => branch.name == this.branchName)
|
||||
if (!this.stream) return
|
||||
return this.stream.branches.items.find((branch) => branch.name === this.branchName)
|
||||
},
|
||||
selectedCommit() {
|
||||
if (this.$apollo.loading) return
|
||||
if (this.commitId == 'latest') return this.selectedBranch.commits.items[0]
|
||||
return this.selectedBranch.commits.items.find((commit) => commit.id == this.commitId)
|
||||
if (!this.selectedBranch) return
|
||||
if (this.commitId === 'latest') return this.selectedBranch.commits.items[0]
|
||||
return this.selectedBranch.commits.items.find((commit) => commit.id === this.commitId)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
bus.$on(`deactivate-diffing-${this.streamId}`, () => {
|
||||
this.diffing = false
|
||||
})
|
||||
bus.$on(`validate-stream-${this.streamId}`, () => {
|
||||
this.invalid = false
|
||||
console.log(`validate-stream-${this.streamId}`)
|
||||
})
|
||||
bus.$on(`invalidate-stream-${this.streamId}`, () => {
|
||||
this.invalid = true
|
||||
console.log(`invalidate-stream-${this.streamId}`)
|
||||
})
|
||||
bus.$on(`refresh-stream-${this.streamId}`, () => {
|
||||
let oldBranchName = this.branchName
|
||||
let oldCommitId = this.commitId
|
||||
this.$apollo.queries.stream.refetch()
|
||||
this.branchName = oldBranchName
|
||||
this.commitId = oldCommitId
|
||||
})
|
||||
bus.$on(`create-branch-${this.streamId}`, (branchName) => {
|
||||
this.$apollo.queries.stream.refetch()
|
||||
this.branchName = branchName
|
||||
this.commitId = 'latest'
|
||||
})
|
||||
bus.$on(`sketchup-objects-${this.streamId}`, async (batches, commitId, totalChildrenCount) => {
|
||||
console.log('>>> SpeckleSketchUp: Received objects from sketchup')
|
||||
|
||||
@@ -294,6 +332,13 @@ export default {
|
||||
if (this.saved) sketchup.exec({name: "notify_connected", data: {stream_id: this.streamId}})
|
||||
},
|
||||
methods: {
|
||||
getColor(invalid){
|
||||
if(invalid){
|
||||
return "#ffdfdf"
|
||||
} else {
|
||||
return ""
|
||||
}
|
||||
},
|
||||
sleep(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
},
|
||||
@@ -310,6 +355,16 @@ export default {
|
||||
this.$mixpanel.track('Connector Action', { name: 'Commit Switch' })
|
||||
this.commitId = commitId
|
||||
},
|
||||
activateDiffing(){
|
||||
if (this.diffing){
|
||||
this.diffing = false
|
||||
sketchup.exec({name: "deactivate_diffing", data: {}})
|
||||
return
|
||||
}
|
||||
this.diffing = true
|
||||
bus.$emit("deactivate-diffing-except", (this.streamId))
|
||||
sketchup.exec({name: "activate_diffing", data: {stream_id: this.streamId}})
|
||||
},
|
||||
toggleSavedStream() {
|
||||
if (this.saved) {
|
||||
sketchup.exec({name: "remove_stream", data: {stream_id: this.streamId}})
|
||||
@@ -322,7 +377,11 @@ export default {
|
||||
async receive() {
|
||||
this.loadingStage = 'requesting'
|
||||
this.loadingReceive = true
|
||||
this.$mixpanel.track('Receive')
|
||||
const selectedAccount = JSON.parse(localStorage.getItem('selectedAccount'))
|
||||
const isMultiplayer = this.selectedCommit.authorId !== selectedAccount['userInfo']['id']
|
||||
const sourceApp = this.selectedCommit.sourceApplication
|
||||
const sourceAppSlug = HostApplications.GetHostAppFromString(sourceApp).slug
|
||||
this.$mixpanel.track('Receive', { isMultiplayer: isMultiplayer, sourceHostApp: sourceAppSlug, sourceHostAppVersion: sourceApp})
|
||||
const refId = this.selectedCommit?.referencedObject
|
||||
if (!refId) {
|
||||
this.loadingReceive = false
|
||||
@@ -344,7 +403,8 @@ export default {
|
||||
stream_name: this.stream.name,
|
||||
stream_id: this.streamId,
|
||||
branch_name: this.selectedCommit.branchName,
|
||||
branch_id: this.selectedCommit.id
|
||||
branch_id: this.selectedCommit.id,
|
||||
source_app: this.selectedCommit.sourceApplication
|
||||
}})
|
||||
|
||||
await this.$apollo.mutate({
|
||||
@@ -376,31 +436,23 @@ export default {
|
||||
await this.sleep(2000)
|
||||
},
|
||||
async createCommit(batches, commitId, totalChildrenCount) {
|
||||
if (batches.length == 0) {
|
||||
if (batches.length === 0) {
|
||||
this.loadingSend = false
|
||||
this.loadingStage = null
|
||||
this.$eventHub.$emit('notification', {
|
||||
text: 'No objects selected. Nothing was sent.'
|
||||
text: 'No objects selected. Nothing was sent.\n'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// let s = new BaseObjectSerializer()
|
||||
// let startTime = new Date()
|
||||
// let { hash, serialized } = s.writeJson(objects)
|
||||
// let endTime = new Date();
|
||||
// let timeDiff = endTime - startTime; //in ms
|
||||
// // strip the ms
|
||||
// timeDiff /= 1000;
|
||||
// // get seconds
|
||||
// console.log(timeDiff + " seconds");
|
||||
|
||||
try {
|
||||
this.loadingStage = 'uploading'
|
||||
this.loadingSend = true
|
||||
const totBatches = batches.length
|
||||
console.log(`>>> SpeckleSketchUp: ${totBatches} batches ready for sending`)
|
||||
let batchesSent = 0
|
||||
|
||||
const t0 = Date.now()
|
||||
for (const batch of batches) {
|
||||
let res = await this.sendBatch(batch)
|
||||
if (res.status !== 201) throw `Upload request failed: ${res.status}`
|
||||
@@ -408,6 +460,10 @@ export default {
|
||||
this.loadingStage = `uploading: ${Math.round((batchesSent / totBatches) * 100)}%`
|
||||
}
|
||||
|
||||
const t1 = Date.now()
|
||||
const elapsedTime = (t1-t0) / 1000
|
||||
console.log(`Upload time: ${elapsedTime} second`)
|
||||
|
||||
let commit = {
|
||||
streamId: this.streamId,
|
||||
branchName: this.branchName,
|
||||
@@ -428,7 +484,7 @@ export default {
|
||||
})
|
||||
console.log('>>> SpeckleSketchUp: Sent to stream: ' + this.streamId, commit)
|
||||
this.$eventHub.$emit('notification', {
|
||||
text: 'Model selection sent!',
|
||||
text: 'Model selection sent!\n',
|
||||
action: {
|
||||
name: 'View in Web',
|
||||
url: `${localStorage.getItem('serverUrl')}/streams/${this.streamId}/commits/${
|
||||
@@ -461,6 +517,22 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.delta-btn.v-btn--outlined {
|
||||
border: 1px solid #474747;
|
||||
background: white;
|
||||
margin-left: -90px;
|
||||
margin-top: -50px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.toggleUpDown {
|
||||
transition: transform .3s ease-in-out !important;
|
||||
}
|
||||
|
||||
.toggleUpDown.rotate {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 0.2s ease-in;
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
<template>
|
||||
<!-- DIALOG: Create Branch -->
|
||||
<v-dialog v-model="showCreateBranch">
|
||||
<template #activator="{ on: dialog, attrs }">
|
||||
<v-btn
|
||||
v-tooltip="'Create Branch'"
|
||||
icon x-small class="ml-0 mr-1"
|
||||
v-bind="attrs"
|
||||
v-on="{...dialog}"
|
||||
>
|
||||
<v-icon>
|
||||
mdi-plus-circle
|
||||
</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
<v-card>
|
||||
<v-card-title class="text-h5 mb-1">
|
||||
Create a New Branch
|
||||
</v-card-title>
|
||||
<v-card-subtitle class="py-0 my-0 font-italic">
|
||||
under {{ streamName }} stream
|
||||
</v-card-subtitle>
|
||||
<v-container class="px-6" pb-0>
|
||||
<v-text-field
|
||||
v-model="branchName"
|
||||
xxxclass="small-text-field"
|
||||
hide-details
|
||||
dense
|
||||
flat
|
||||
placeholder="Branch Name"
|
||||
/>
|
||||
<v-text-field
|
||||
v-model="description"
|
||||
xxxclass="small-text-field"
|
||||
hide-details
|
||||
dense
|
||||
flat
|
||||
placeholder="Description (Optional)"
|
||||
/>
|
||||
</v-container>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
color="blue darken-1"
|
||||
text
|
||||
@click="showCreateBranch = false"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn
|
||||
:disabled="branchName === ''"
|
||||
color="blue darken-1"
|
||||
text
|
||||
@click="createBranch"
|
||||
>
|
||||
Create
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import gql from "graphql-tag";
|
||||
import {bus} from "@/main";
|
||||
|
||||
export default {
|
||||
name: "CreateBranchDialog",
|
||||
props: {
|
||||
streamId: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
streamName: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showCreateBranch: false,
|
||||
branchName: "",
|
||||
description: "",
|
||||
defaultDescription: "Stream created from SketchUp",
|
||||
accountToCreateStream: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
loggedIn() {
|
||||
return localStorage.getItem('SpeckleSketchup.AuthToken') !== null
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async createBranch(){
|
||||
let res = await this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
mutation branchCreate($branch: BranchCreateInput!) {
|
||||
branchCreate(branch: $branch)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
branch: {
|
||||
streamId: this.streamId,
|
||||
name: this.branchName,
|
||||
description: this.description === '' ? this.defaultDescription : this.description,
|
||||
}
|
||||
}
|
||||
})
|
||||
bus.$emit(`create-branch-${this.streamId}`, this.branchName)
|
||||
this.showCreateBranch = false
|
||||
this.branchName = ""
|
||||
this.description = ""
|
||||
this.$mixpanel.track('Connector Action', { name: 'Branch Create' })
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.v-dialog {
|
||||
max-width: 390px
|
||||
}
|
||||
|
||||
.v-text-field >>> input {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
.v-text-field >>> label {
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
</style>
|
||||
+76
-14
@@ -92,7 +92,6 @@
|
||||
</v-dialog>
|
||||
|
||||
<!-- DIALOG: Add a Stream by ID or URL -->
|
||||
<!--
|
||||
<v-dialog v-model="showCreateStreamById">
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn
|
||||
@@ -141,14 +140,13 @@
|
||||
:disabled="createStreamByIdText === ''"
|
||||
color="blue darken-1"
|
||||
text
|
||||
@click="showCreateStreamById = false"
|
||||
@click="getStream"
|
||||
>
|
||||
Add
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
-->
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
@@ -159,9 +157,20 @@
|
||||
import gql from "graphql-tag";
|
||||
import {bus} from "@/main";
|
||||
import userQuery from "@/graphql/user.gql";
|
||||
import {StreamWrapper} from "@/utils/streamWrapper";
|
||||
|
||||
export default {
|
||||
name: "CreateStream",
|
||||
name: "CreateStreamDialog",
|
||||
props: {
|
||||
accountId: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
serverUrl: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showCreateNewStream: false,
|
||||
@@ -177,10 +186,7 @@ export default {
|
||||
computed: {
|
||||
loggedIn() {
|
||||
return localStorage.getItem('SpeckleSketchup.AuthToken') !== null
|
||||
},
|
||||
accounts() {
|
||||
return JSON.parse(localStorage.getItem('localAccounts'))
|
||||
},
|
||||
}
|
||||
},
|
||||
apollo: {
|
||||
user: {
|
||||
@@ -188,16 +194,71 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
activeAccount(){
|
||||
return JSON.parse(localStorage.getItem('selectedAccount'))
|
||||
},
|
||||
switchAccountToCreateStream(account) {
|
||||
this.accountToCreateStream = account
|
||||
},
|
||||
refresh() {
|
||||
this.$apollo.queries.user.refetch()
|
||||
bus.$emit('refresh-streams')
|
||||
},
|
||||
async getStream(){
|
||||
try {
|
||||
const streamWrapper = new StreamWrapper(this.createStreamByIdText, this.accountId, this.serverUrl)
|
||||
let res = await this.$apollo.query({
|
||||
query: gql`
|
||||
query Stream($id: String!){
|
||||
stream(id: $id){
|
||||
id
|
||||
name
|
||||
description
|
||||
isPublic
|
||||
role
|
||||
createdAt
|
||||
updatedAt
|
||||
commentCount
|
||||
favoritedDate
|
||||
favoritesCount
|
||||
collaborators {
|
||||
id
|
||||
name
|
||||
role
|
||||
avatar
|
||||
},
|
||||
branches (limit: ${10}){
|
||||
totalCount,
|
||||
cursor,
|
||||
items {
|
||||
id,
|
||||
name,
|
||||
description,
|
||||
commits {
|
||||
totalCount
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
id: streamWrapper.streamId
|
||||
}
|
||||
})
|
||||
let stream = res.data.stream
|
||||
|
||||
this.$eventHub.$emit('notification', {
|
||||
text: 'Stream Added by URL!\n',
|
||||
})
|
||||
bus.$emit('stream-added-by-id-or-url', stream.id)
|
||||
this.$mixpanel.track('Connector Action', { name: 'Stream Add From URL' })
|
||||
}
|
||||
catch (e){
|
||||
this.$eventHub.$emit('error', {
|
||||
text: 'The stream you are trying to add might;\n' +
|
||||
'- lies on different server, \n' +
|
||||
'- be private, \n' +
|
||||
'- not be existed anymore.',
|
||||
})
|
||||
}
|
||||
|
||||
this.showCreateStreamById = false
|
||||
},
|
||||
async createStream(){
|
||||
let res = await this.$apollo.mutate({
|
||||
mutation: gql`
|
||||
@@ -216,6 +277,7 @@ export default {
|
||||
this.showCreateNewStream = false
|
||||
this.streamName = ""
|
||||
this.description = ""
|
||||
this.$mixpanel.track('Connector Action', { name: 'Stream Create' })
|
||||
sketchup.exec({name: "save_stream", data: {stream_id: res["data"]["streamCreate"]}})
|
||||
this.refresh()
|
||||
return res
|
||||
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<!-- DIALOG: Settings -->
|
||||
<v-dialog v-model="showSettings">
|
||||
<template #activator="{ on, attrs }">
|
||||
<v-btn
|
||||
icon small class="mx-1"
|
||||
v-bind="attrs"
|
||||
v-on="on"
|
||||
>
|
||||
<v-icon>mdi-cog</v-icon>
|
||||
</v-btn>
|
||||
</template>
|
||||
|
||||
<v-card>
|
||||
<v-card-title class="text-h5">
|
||||
Settings
|
||||
</v-card-title>
|
||||
<v-container class="px-6" pb-0>
|
||||
<!-- Switch Theme -->
|
||||
<div class="sm1 mt-3">User Preferences</div>
|
||||
<v-divider class="mb-2"/>
|
||||
<v-btn icon small class="mx-1" @click="switchTheme">
|
||||
<v-icon>mdi-theme-light-dark</v-icon>
|
||||
</v-btn>
|
||||
<span>Color Mode</span>
|
||||
|
||||
<!-- Register objects as Speckle Entity on send/receive -->
|
||||
<v-switch
|
||||
:input-value="registerSpeckleEntity"
|
||||
class="pt-3 mt-n2 mb-n7"
|
||||
:label="'Register objects as Speckle Entity on send/receive'"
|
||||
@change="registerSpeckleEntityHandler"
|
||||
/>
|
||||
|
||||
<!-- Switch Diffing -->
|
||||
<v-switch
|
||||
:input-value="diffing"
|
||||
class="pt-3 mt-n2 mb-n2"
|
||||
:label="'Diffing (Alpha)'"
|
||||
@change="diffingHandler"
|
||||
/>
|
||||
|
||||
<div class="sm1 mt-3">Model Preferences</div>
|
||||
<v-divider class="mb-2"/>
|
||||
<v-switch
|
||||
class="pt-1 mt-n2 mb-n2"
|
||||
:input-value="combineFacesByMaterial"
|
||||
:label="'Combine faces by material under mesh'"
|
||||
@change="combineFacesByMaterialHandler"
|
||||
/>
|
||||
<v-switch
|
||||
:input-value="includeAttributes"
|
||||
class="pt-1 my-n5"
|
||||
:label="'Include entity attributes'"
|
||||
@change="includeAttributesHandler"
|
||||
/>
|
||||
<v-icon class="ml-3" style="line-height: 0;">mdi-arrow-right-bottom</v-icon>
|
||||
<v-switch
|
||||
:input-value="includeEdgeAttributes"
|
||||
class="pt-1 my-n5 ml-10"
|
||||
:label="'Edge'"
|
||||
:disabled="!includeAttributes"
|
||||
@change="includeEdgeAttributesHandler"
|
||||
/>
|
||||
<v-icon class="ml-3" style="line-height: 0;">mdi-arrow-right-bottom</v-icon>
|
||||
<v-switch
|
||||
:input-value="includeFaceAttributes"
|
||||
class="pt-1 my-n5 ml-10"
|
||||
:label="'Face'"
|
||||
:disabled="!includeAttributes"
|
||||
@change="includeFaceAttributesHandler"
|
||||
/>
|
||||
<v-icon class="ml-3" style="line-height: 0;">mdi-arrow-right-bottom</v-icon>
|
||||
<v-switch
|
||||
:input-value="includeGroupAttributes"
|
||||
class="pt-1 my-n5 ml-10"
|
||||
:label="'Group'"
|
||||
:disabled="!includeAttributes"
|
||||
@change="includeGroupAttributesHandler"
|
||||
/>
|
||||
<v-icon class="ml-3" style="line-height: 0;">mdi-arrow-right-bottom</v-icon>
|
||||
<v-switch
|
||||
:input-value="includeComponentAttributes"
|
||||
class="pt-1 my-n5 ml-10"
|
||||
:label="'Component'"
|
||||
:disabled="!includeAttributes"
|
||||
@change="includeComponentAttributesHandler"
|
||||
/>
|
||||
<v-switch
|
||||
:input-value="mergeCoplanarFaces"
|
||||
class="pt-1 mt-n2 mb-n2"
|
||||
:label="'Merge co-planar faces on receive'"
|
||||
@change="mergeCoplanarFacesHandler"
|
||||
/>
|
||||
</v-container>
|
||||
|
||||
<v-card-actions>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn
|
||||
color="blue darken-1"
|
||||
text
|
||||
@click="showSettings = false"
|
||||
>
|
||||
Close
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/*global sketchup*/
|
||||
import {bus} from "@/main";
|
||||
|
||||
export default {
|
||||
name: "ShowSettings",
|
||||
props: {
|
||||
preferences: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showSettings: false,
|
||||
streamName: "",
|
||||
description: "",
|
||||
combineFacesByMaterial: this.preferences.model.combine_faces_by_material,
|
||||
includeAttributes: this.preferences.model.include_entity_attributes,
|
||||
includeFaceAttributes: this.preferences.model.include_face_entity_attributes,
|
||||
includeEdgeAttributes: this.preferences.model.include_edge_entity_attributes,
|
||||
includeGroupAttributes: this.preferences.model.include_group_entity_attributes,
|
||||
includeComponentAttributes: this.preferences.model.include_component_entity_attributes,
|
||||
mergeCoplanarFaces: this.preferences.model.merge_coplanar_faces,
|
||||
diffing: this.preferences.user.diffing,
|
||||
registerSpeckleEntity: this.preferences.user.register_speckle_entity
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
'preferences': {
|
||||
handler(newValue) {
|
||||
this.combineFacesByMaterial = newValue.model.combine_faces_by_material
|
||||
this.includeAttributes = newValue.model.include_entity_attributes
|
||||
this.includeFaceAttributes = newValue.model.include_face_entity_attributes
|
||||
this.includeEdgeAttributes = newValue.model.include_edge_entity_attributes
|
||||
this.includeGroupAttributes = newValue.model.include_group_entity_attributes
|
||||
this.includeComponentAttributes = newValue.model.include_component_entity_attributes
|
||||
this.mergeCoplanarFaces = newValue.model.merge_coplanar_faces
|
||||
this.diffing = newValue.user.diffing
|
||||
this.registerSpeckleEntity = newValue.user.register_speckle_entity
|
||||
},
|
||||
deep: true,
|
||||
immediate: true
|
||||
},
|
||||
'showSettings': {
|
||||
handler(newValue) {
|
||||
if (newValue){
|
||||
this.$mixpanel.track('Connector Action', { name: 'Settings Open' })
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
diffingHandler(newValue){
|
||||
this.diffing = !!newValue
|
||||
sketchup.exec({
|
||||
name: "user_preferences_updated",
|
||||
data: {preference_hash: "configSketchup", preference: "diffing", value: this.diffing}
|
||||
})
|
||||
this.$mixpanel.track('Connector Action', { name: 'Toggle Diffing' })
|
||||
},
|
||||
registerSpeckleEntityHandler(newValue){
|
||||
this.registerSpeckleEntity = !!newValue
|
||||
sketchup.exec({
|
||||
name: "user_preferences_updated",
|
||||
data: {preference_hash: "configSketchup", preference: "register_speckle_entity", value: this.registerSpeckleEntity}
|
||||
})
|
||||
this.$mixpanel.track('Connector Action', { name: 'Toggle Register Speckle Entity' })
|
||||
},
|
||||
combineFacesByMaterialHandler(newValue){
|
||||
this.combineFacesByMaterial = !!newValue
|
||||
sketchup.exec({
|
||||
name: "model_preferences_updated",
|
||||
data: {preference: "combine_faces_by_material", value: this.combineFacesByMaterial}
|
||||
})
|
||||
this.$mixpanel.track('Connector Action', { name: 'Toggle Combine Faces By Material' })
|
||||
},
|
||||
includeAttributesHandler(newValue){
|
||||
this.includeAttributes = !!newValue
|
||||
sketchup.exec({
|
||||
name: "model_preferences_updated",
|
||||
data: {preference: "include_entity_attributes", value: this.includeAttributes}
|
||||
})
|
||||
this.$mixpanel.track('Connector Action', { name: 'Toggle Include Entity Attributes' })
|
||||
},
|
||||
includeFaceAttributesHandler(newValue){
|
||||
this.includeFaceAttributes = !!newValue
|
||||
sketchup.exec({
|
||||
name: "model_preferences_updated",
|
||||
data: {preference: "include_face_entity_attributes", value: this.includeFaceAttributes}
|
||||
})
|
||||
this.$mixpanel.track('Connector Action', { name: 'Toggle Include Face Entity Attributes' })
|
||||
},
|
||||
includeEdgeAttributesHandler(newValue){
|
||||
this.includeEdgeAttributes = !!newValue
|
||||
sketchup.exec({
|
||||
name: "model_preferences_updated",
|
||||
data: {preference: "include_edge_entity_attributes", value: this.includeEdgeAttributes}
|
||||
})
|
||||
this.$mixpanel.track('Connector Action', { name: 'Toggle Include Edge Entity Attributes' })
|
||||
},
|
||||
includeGroupAttributesHandler(newValue){
|
||||
this.includeGroupAttributes = !!newValue
|
||||
sketchup.exec({
|
||||
name: "model_preferences_updated",
|
||||
data: {preference: "include_group_entity_attributes", value: this.includeGroupAttributes}
|
||||
})
|
||||
this.$mixpanel.track('Connector Action', { name: 'Toggle Include Group Entity Attributes' })
|
||||
},
|
||||
includeComponentAttributesHandler(newValue){
|
||||
this.includeComponentAttributes = !!newValue
|
||||
sketchup.exec({
|
||||
name: "model_preferences_updated",
|
||||
data: {preference: "include_component_entity_attributes", value: this.includeComponentAttributes}
|
||||
})
|
||||
this.$mixpanel.track('Connector Action', { name: 'Toggle Include Component Entity Attributes' })
|
||||
},
|
||||
mergeCoplanarFacesHandler(newValue){
|
||||
this.mergeCoplanarFaces = !!newValue
|
||||
sketchup.exec({
|
||||
name: "model_preferences_updated",
|
||||
data: {preference: "merge_coplanar_faces", value: this.mergeCoplanarFaces}
|
||||
})
|
||||
this.$mixpanel.track('Connector Action', { name: 'Toggle Merge Co-Planar Faces' })
|
||||
},
|
||||
switchTheme() {
|
||||
this.$vuetify.theme.dark = !this.$vuetify.theme.dark
|
||||
sketchup.exec({
|
||||
name: "user_preferences_updated",
|
||||
data: {preference_hash: "configSketchup", preference: "dark_theme", value: this.$vuetify.theme.dark}
|
||||
})
|
||||
this.$mixpanel.track('Connector Action', { name: 'Toggle Theme' })
|
||||
},
|
||||
refresh() {
|
||||
this.$apollo.queries.user.refetch()
|
||||
bus.$emit('refresh-streams')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
</style>
|
||||
@@ -42,6 +42,7 @@ query Stream($id: String!) {
|
||||
branchName
|
||||
authorName
|
||||
authorAvatar
|
||||
sourceApplication
|
||||
referencedObject
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,12 @@ const SpeckleMetrics = {
|
||||
|
||||
Vue.prototype.$mixpanel = mixpanel
|
||||
Vue.prototype.$mixpanel.init(token, config)
|
||||
Vue.prototype.$mixpanel.register({ hostApp: 'sketchup', type: 'action' })
|
||||
|
||||
Vue.prototype.$refreshMixpanelIds = function () {
|
||||
// 1. It logs out the user and removes out the registered props.
|
||||
Vue.prototype.$mixpanel.reset()
|
||||
|
||||
// 2. create hashes for user distinction
|
||||
let distinctId =
|
||||
'@' +
|
||||
crypto
|
||||
@@ -29,12 +32,20 @@ const SpeckleMetrics = {
|
||||
.digest('hex')
|
||||
.toUpperCase()
|
||||
|
||||
// 3. Setting super properties that will be sent with every event
|
||||
Vue.prototype.$mixpanel.register({
|
||||
// eslint-disable-next-line camelcase
|
||||
distinct_id: distinctId,
|
||||
// eslint-disable-next-line camelcase
|
||||
hostApp: 'sketchup',
|
||||
type: 'action',
|
||||
hostAppVersion: localStorage.getItem('hostAppVersion'),
|
||||
core_version: localStorage.getItem('speckleVersion'),
|
||||
server_id: serverId
|
||||
})
|
||||
|
||||
// 4. Logging into user
|
||||
Vue.prototype.$mixpanel.identify(distinctId)
|
||||
|
||||
// 5. If it is a registered user we can consider it is identified
|
||||
Vue.prototype.$mixpanel.people.set("Identified", true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ Vue.use(Vuetify)
|
||||
|
||||
export default new Vuetify({
|
||||
theme: {
|
||||
dark: localStorage.getItem('theme') === 'dark',
|
||||
dark: false,
|
||||
themes: {
|
||||
light: {
|
||||
primary: '#0480FB', //speckle blue
|
||||
|
||||
@@ -8,7 +8,7 @@ const routes = [
|
||||
path: '*',
|
||||
name: 'Streams',
|
||||
component: () => import('../views/Streams.vue')
|
||||
}
|
||||
},
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
class HostApplication {
|
||||
constructor(name, slug) {
|
||||
this.name = name;
|
||||
this.slug = slug;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Static class to retrieve host application names and their shortened name(slug).
|
||||
* Call HostApplications.GetHostAppFromString(appname) method to get name and slug.
|
||||
* @example
|
||||
* const hostApp = HostApplications.GetHostAppFromString("Revit2022")
|
||||
* console.log(hostApp.name) -> "Revit"
|
||||
* console.log(hostApp.slug) -> "revit"
|
||||
*/
|
||||
export class HostApplications {
|
||||
static Rhino = new HostApplication("Rhino", "rhino");
|
||||
static Grasshopper = new HostApplication("Grasshopper", "grasshopper");
|
||||
static Revit = new HostApplication("Revit", "revit");
|
||||
static Dynamo = new HostApplication("Dynamo", "dynamo");
|
||||
static Unity = new HostApplication("Unity", "unity");
|
||||
static GSA = new HostApplication("GSA", "gsa");
|
||||
static Civil = new HostApplication("Civil 3D", "civil3d");
|
||||
static AutoCAD = new HostApplication("AutoCAD", "autocad");
|
||||
static MicroStation = new HostApplication("MicroStation", "microstation");
|
||||
static OpenRoads = new HostApplication("OpenRoads", "openroads");
|
||||
static OpenRail = new HostApplication("OpenRail", "openrail");
|
||||
static OpenBuildings = new HostApplication("OpenBuildings", "openbuildings");
|
||||
static ETABS = new HostApplication("ETABS", "etabs");
|
||||
static SAP2000 = new HostApplication("SAP2000", "sap2000");
|
||||
static CSIBridge = new HostApplication("CSIBridge", "csibridge");
|
||||
static SAFE = new HostApplication("SAFE", "safe");
|
||||
static TeklaStructures = new HostApplication("Tekla Structures", "teklastructures");
|
||||
static Dxf = new HostApplication("DXF Converter", "dxf");
|
||||
static Excel = new HostApplication("Excel", "excel");
|
||||
static Unreal = new HostApplication("Unreal", "unreal");
|
||||
static PowerBI = new HostApplication("Power BI", "powerbi");
|
||||
static Blender = new HostApplication("Blender", "blender");
|
||||
static QGIS = new HostApplication("QGIS", "qgis");
|
||||
static ArcGIS = new HostApplication("ArcGIS", "arcgis");
|
||||
static SketchUp = new HostApplication("SketchUp", "sketchup");
|
||||
static Archicad = new HostApplication("Archicad", "archicad");
|
||||
static TopSolid = new HostApplication("TopSolid", "topsolid");
|
||||
static Python = new HostApplication("Python", "python");
|
||||
static NET = new HostApplication(".NET", "net");
|
||||
static Navisworks = new HostApplication("Navisworks", "navisworks");
|
||||
static AdvanceSteel = new HostApplication("Advance Steel", "advancesteel");
|
||||
static Other = new HostApplication("Other", "other");
|
||||
|
||||
static GetHostAppFromString(appname){
|
||||
if (!appname) return HostApplications.Other;
|
||||
appname = appname.toLowerCase().replace(/ /g, "");
|
||||
if (appname.includes("dynamo")) return HostApplications.Dynamo;
|
||||
if (appname.includes("revit")) return HostApplications.Revit;
|
||||
if (appname.includes("autocad")) return HostApplications.AutoCAD;
|
||||
if (appname.includes("civil")) return HostApplications.Civil;
|
||||
if (appname.includes("rhino")) return HostApplications.Rhino;
|
||||
if (appname.includes("grasshopper")) return HostApplications.Grasshopper;
|
||||
if (appname.includes("unity")) return HostApplications.Unity;
|
||||
if (appname.includes("gsa")) return HostApplications.GSA;
|
||||
if (appname.includes("microstation")) return HostApplications.MicroStation;
|
||||
if (appname.includes("openroads")) return HostApplications.OpenRoads;
|
||||
if (appname.includes("openrail")) return HostApplications.OpenRail;
|
||||
if (appname.includes("openbuildings")) return HostApplications.OpenBuildings;
|
||||
if (appname.includes("etabs")) return HostApplications.ETABS;
|
||||
if (appname.includes("sap")) return HostApplications.SAP2000;
|
||||
if (appname.includes("csibridge")) return HostApplications.CSIBridge;
|
||||
if (appname.includes("safe")) return HostApplications.SAFE;
|
||||
if (appname.includes("teklastructures")) return HostApplications.TeklaStructures;
|
||||
if (appname.includes("dxf")) return HostApplications.Dxf;
|
||||
if (appname.includes("excel")) return HostApplications.Excel;
|
||||
if (appname.includes("unreal")) return HostApplications.Unreal;
|
||||
if (appname.includes("powerbi")) return HostApplications.PowerBI;
|
||||
if (appname.includes("blender")) return HostApplications.Blender;
|
||||
if (appname.includes("qgis")) return HostApplications.QGIS;
|
||||
if (appname.includes("arcgis")) return HostApplications.ArcGIS;
|
||||
if (appname.includes("sketchup")) return HostApplications.SketchUp;
|
||||
if (appname.includes("archicad")) return HostApplications.Archicad;
|
||||
if (appname.includes("topsolid")) return HostApplications.TopSolid;
|
||||
if (appname.includes("python")) return HostApplications.Python;
|
||||
if (appname.includes(".net")) return HostApplications.NET;
|
||||
if (appname.includes("navisworks")) return HostApplications.Navisworks;
|
||||
if (appname.includes("advancesteel")) return HostApplications.AdvanceSteel;
|
||||
return appname;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
require('url')
|
||||
|
||||
export class StreamWrapper {
|
||||
constructor(streamIdOrUrl, accountId, serverUrl) {
|
||||
this.originalOutput = streamIdOrUrl
|
||||
try {
|
||||
this.streamWrapperFromUrl(streamIdOrUrl)
|
||||
}
|
||||
catch (e){
|
||||
this.serverUrl = serverUrl
|
||||
this.userId = accountId
|
||||
this.streamId = streamIdOrUrl
|
||||
}
|
||||
}
|
||||
|
||||
streamWrapperFromUrl(streamUrl){
|
||||
this.url = new URL(streamUrl)
|
||||
this.segments = this.url.pathname.split('/').map((segment) => segment + '/')
|
||||
this.serverUrl = this.url.origin
|
||||
|
||||
if (this.segments.length >= 4 && this.segments[3]?.toLowerCase() === "branches/"){
|
||||
this.streamId = this.segments[2].replace("/", "")
|
||||
if (this.segments.length > 5)
|
||||
{
|
||||
let branchSegments = this.segments.slice(4, this.segments.length - 1);
|
||||
this.branchName = branchSegments.join("")
|
||||
}
|
||||
else
|
||||
{
|
||||
this.branchName = this.segments[4]
|
||||
}
|
||||
} else {
|
||||
switch (this.segments.length){
|
||||
case 3: // ie http://speckle.server/streams/8fecc9aa6d
|
||||
if (this.segments[1].toLowerCase() === "streams/")
|
||||
this.streamId = this.segments[2].replace("/", "");
|
||||
else
|
||||
throw new Error(`Cannot parse ${this.originalOutput} into a stream wrapper class`);
|
||||
break;
|
||||
case 4: // ie https://speckle.server/streams/0c6ad366c4/globals/
|
||||
if (this.segments[3].toLowerCase().startsWith("globals"))
|
||||
{
|
||||
this.streamId = this.segments[2].replace("/", "");
|
||||
this.branchName = this.segments[3].replace("/", "");
|
||||
}
|
||||
else
|
||||
throw new Error(`Cannot parse ${this.originalOutput} into a stream wrapper class`);
|
||||
break;
|
||||
case 5: // ie http://speckle.server/streams/8fecc9aa6d/commits/76a23d7179
|
||||
switch (this.segments[3].toLowerCase()){
|
||||
case "commits/":
|
||||
this.streamId = this.segments[2].replace("/", "");
|
||||
this.commitId = this.segments[4].replace("/", "");
|
||||
break;
|
||||
case "globals/":
|
||||
this.streamId = this.segments[2].replace("/", "");
|
||||
this.branchName = this.segments[3].replace("/", "");
|
||||
this.commitId = this.segments[4].replace("/", "");
|
||||
break;
|
||||
case "branches/":
|
||||
this.streamId = this.segments[2].replace("/", "");
|
||||
this.branchName = this.segments[4].replace("/", "");
|
||||
break;
|
||||
case "objects/":
|
||||
this.streamId = this.segments[2].replace("/", "");
|
||||
this.objectId = this.segments[4].replace("/", "");
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Cannot parse ${this.originalOutput} into a stream wrapper class`);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Cannot parse ${this.originalOutput} into a stream wrapper class`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user