Compare commits

...

106 Commits

Author SHA1 Message Date
Oğuzhan Koral 5187fded02 Fix (Mesh): Non-planar quad mesh support 2023-05-01 18:08:11 +03:00
oguzhankoral 370825838a Add plane_utils to calculate planarity of 4 points for quad meshes 2023-05-01 17:56:28 +03:00
oguzhankoral bc8eece488 Split all quad meshes 2023-04-28 12:23:16 +01:00
Oğuzhan Koral 4222b1721d Fix (Views): Bug on views recieve 2023-04-12 13:39:01 +03:00
oguzhankoral b475bc96af Disable rubocop for to_native 2023-04-12 13:36:49 +03:00
oguzhankoral f9ac7319ae Fix bug on views recieve 2023-04-12 13:34:09 +03:00
Oğuzhan Koral 2ce3e9150f Release 2.13 2023-03-24 18:08:26 +03:00
Oğuzhan Koral 8e39832d3e Fix (Instancing): Definition uniqueness for mirrored definitions 2023-03-20 22:14:10 +03:00
oguzhankoral ce7ac6da16 Create definitions with it's id 2023-03-20 22:11:52 +03:00
oguzhankoral 57879a57cd Enable conversion for full assembly name block instance 2023-03-20 22:05:42 +03:00
Oğuzhan Koral 6f5b367028 Fix (Revit): Do not create layers in advance for revit receives 2023-03-20 13:04:42 +03:00
oguzhankoral 291948c1ef Do not create layers in advance for revit receives 2023-03-20 12:57:26 +03:00
Oğuzhan Koral 0ca6888fb1 Fix (Instancing): Support nested families re-fix 2023-03-20 10:56:50 +03:00
oguzhankoral 5e92236396 Remove skp attributes from transform 2023-03-20 10:45:45 +03:00
oguzhankoral 28db40112b Collect revit elements also into displayValue to handle later 2023-03-20 10:45:13 +03:00
Oğuzhan Koral b2153883ef Revert "Fix (Instancing): Support nested families" 2023-03-16 18:28:44 +03:00
Oğuzhan Koral 807cbeb75d Revert "Fix (Instancing): Support nested families" 2023-03-16 18:28:14 +03:00
Oğuzhan Koral 4730aebdc6 Fix (Instancing): Support nested families 2023-03-16 18:06:50 +03:00
oguzhankoral c2279eec87 Add elements of revit_definition to display value to handle all 2023-03-16 18:03:21 +03:00
Oğuzhan Koral f5567ae9ad Chore (Mixpanel): Send isMultiplayer parameter with Receive action 2023-03-16 11:50:45 +03:00
oguzhankoral 2fcba04cf5 Pass sourceHostApp with slug and sourceHostAppVersion full info on commit
For "Revit2022",
sourceHostApp: "revit"
sourceHostAppVersion: "Revit2022"
2023-03-16 11:13:49 +03:00
oguzhankoral 0eefd1605f Add static HostApplication instances per connector
- This helps to retrieve name and slug of the connector
2023-03-16 11:12:29 +03:00
oguzhankoral 7b88ea022d Send isMultiplayer parameter with Receive action 2023-03-15 12:35:57 +03:00
Oğuzhan Koral 7a922114ab Fix (Instancing): Split revit instance from block instance 2023-03-14 21:18:22 +03:00
oguzhankoral f717c270fd Collect views according to speckle_type 2023-03-14 21:16:56 +03:00
oguzhankoral ee5ed468c3 Fix rubocop issues 2023-03-14 20:18:28 +03:00
oguzhankoral a9ded445a7 Separate revit instance from block instance 2023-03-14 20:08:02 +03:00
oguzhankoral 9ecad52df2 Dont create speckle entity for sketchup materials 2023-03-14 17:48:20 +03:00
oguzhankoral 8c500985bf Flip soft edge check with hard edge 2023-03-14 16:37:16 +03:00
Oğuzhan Koral 3567f9ba5d Fix (Layers): Update level object's layer to Levels 2023-03-14 14:42:01 +03:00
oguzhankoral 852bfb716c Update level object's layer to Levels 2023-03-14 14:39:19 +03:00
Oğuzhan Koral 473956aa3c Fix (Update): Update behavior 2023-03-14 11:33:25 +03:00
oguzhankoral b22fc19400 Register component and groups by default for update behavior 2023-03-14 11:31:17 +03:00
oguzhankoral f6a8ece992 Check existing scene names to prevent duplications 2023-03-14 11:28:28 +03:00
oguzhankoral afc0c21aae Swap branch and stream name for commit obj 2023-03-14 11:10:05 +03:00
Oğuzhan Koral 08e6106c1f Fix (Levels): Levels should be visible 2023-03-13 14:20:01 +03:00
oguzhankoral 1f9f654e3d Add levels as cline 2023-03-13 14:11:10 +03:00
oguzhankoral 501e923760 Add section planes into received component
By this way these section planes will be active only in this commit objects
2023-03-13 09:58:32 +03:00
Oğuzhan Koral 37b716cfc5 Feat (Scene): Create scenes from revit views 2023-03-12 23:46:14 +03:00
oguzhankoral 8584db0ab5 Create scenes from revit views 2023-03-12 23:41:00 +03:00
Oğuzhan Koral 5aff3e9692 Feat (Instancing): Definition naming convention 2023-03-10 18:06:48 +03:00
oguzhankoral 5d8f03cbc3 Fix rubocop suggestions 2023-03-10 18:04:14 +03:00
oguzhankoral b6c85ac15a Group received objects with branch and stream name 2023-03-10 17:57:43 +03:00
oguzhankoral c2af757f4e Collect received objects under component 2023-03-10 16:08:17 +03:00
oguzhankoral 26252c55ac Pass branch and stream name to to_native converter 2023-03-10 16:07:34 +03:00
oguzhankoral 3ce547bcea Update naming convention 2023-03-09 19:04:01 +03:00
oguzhankoral e0492f589e Update will be soften list 2023-03-09 19:03:49 +03:00
oguzhankoral 4931288ffd Update will be hidden layers list 2023-03-09 19:03:26 +03:00
oguzhankoral 52e0f77a3c Remove category from definition names 2023-03-09 16:12:47 +03:00
Oğuzhan Koral 20c016f1bf Feat (Layers): revit categories as tags 2023-03-09 14:24:47 +03:00
oguzhankoral 00a271cbba Create tags for missing categories 2023-03-09 14:24:08 +03:00
oguzhankoral 3a6b69634d Hide specific revit layers
- Rooms, Mass, Mass Floor
2023-03-09 13:36:39 +03:00
Oğuzhan Koral 78cf7f42e3 Feat (Instancing): Support instancing 2023-03-08 23:56:53 +03:00
oguzhankoral 2b52454c00 Extract collect_definition_geometries for display value 2023-03-08 23:56:10 +03:00
oguzhankoral e032e4561b Receive unique elements with element id even if they have same type 2023-03-08 23:41:42 +03:00
oguzhankoral 23ce1e0476 Make soft edge according to definition name 2023-03-08 23:35:47 +03:00
oguzhankoral 825b7b98d7 Fix rubocop suggestions 2023-03-08 23:35:26 +03:00
oguzhankoral 3de3128a2a Cover mullions by including their elements 2023-03-08 23:34:56 +03:00
oguzhankoral 92d70ccc8b Support Objects.Other.Revit.RevitInstance 2023-03-08 23:33:14 +03:00
oguzhankoral f8c5154457 Remove TransparencySort option from model
- It does not make sense anymore
2023-03-08 23:32:35 +03:00
Oğuzhan Koral 3a3874e9f1 Feat (RevitLevels): Revit levels to Sketchup section planes 2023-03-08 20:31:51 +03:00
oguzhankoral 61b94c519b Update TransparencySort value to 3 by default 2023-03-08 14:39:58 +03:00
oguzhankoral ac0f46ca66 Update unique definitions for revit objects 2023-03-08 14:17:05 +03:00
oguzhankoral e538cdb509 Create levels from objects 2023-03-08 14:16:46 +03:00
oguzhankoral df8a63c5f5 By default set DisplaySectionPlanes is true 2023-03-08 14:16:23 +03:00
oguzhankoral 6827a61bf8 Simplify convert_to_speckle_entities logic 2023-03-08 12:32:52 +03:00
Oğuzhan Koral 20e56c05b3 Feat (Receive): Receive objects as speckle entity 2023-03-07 15:30:59 +03:00
oguzhankoral 5494139319 Disable rubocop issues 2023-03-07 15:28:25 +03:00
oguzhankoral c491ba087a Implement another user setting to activate continuous traversal 2023-03-07 15:17:38 +03:00
oguzhankoral 5cf045c941 Collect speckle materials from sketchup model 2023-03-07 15:17:16 +03:00
oguzhankoral 55fea5f077 Add definitions as collection 2023-03-07 15:16:47 +03:00
oguzhankoral 3a9cb73950 Implement dynamic to_native methods to objects 2023-03-07 15:14:12 +03:00
oguzhankoral d99f613b79 Return combined faces from clean_up 2023-03-07 15:13:33 +03:00
oguzhankoral 68db8ddff3 Add type constants 2023-03-07 15:12:50 +03:00
oguzhankoral c7490b264b Pass stream id to converter by default 2023-03-07 15:11:34 +03:00
Oğuzhan Koral a8e84cfad3 Chore (Query): Query source application from commit 2023-02-28 16:03:56 +03:00
oguzhankoral 5ec4f7e4e8 Pass source application info to command and action 2023-02-28 15:54:04 +03:00
oguzhankoral b019ce2f2e Update stream query for commit sourceApplication 2023-02-28 15:53:41 +03:00
Oğuzhan Koral dd90e41610 Fix (Instance): Component instance naming 2023-02-27 10:43:20 +03:00
oguzhankoral bdf78e374d Update diffing icon with eye instead triangle 2023-02-27 10:27:16 +03:00
oguzhankoral 163197f583 Do not set speckle id as instance name as fallback
Now as is
2023-02-27 10:26:53 +03:00
oguzhankoral 8e13bba44a Write always entity dictionaries while converting
This was tightly coupled with diffing before, now it is separated. It's better.
2023-02-27 10:26:03 +03:00
Oğuzhan Koral e4478b86a5 Feat (SpeckleEntity): Read speckle entities when model loaded 2023-02-27 09:01:12 +03:00
oguzhankoral 1b1ae59ed7 Read speckle entities from sketchup model 2023-02-24 19:18:16 +03:00
oguzhankoral a54f44d22a Update init method of speckle entity with more general way 2023-02-24 19:18:03 +03:00
oguzhankoral d2394274eb Simplify speckle entity object
- So entity specific objects removed
2023-02-24 19:17:18 +03:00
Oğuzhan Koral 0eedb0b397 Chore (Mixpanel): Mixpanel alignment 2023-02-24 10:59:45 +03:00
oguzhankoral 782e9db1f6 Align mixpanel event names 2023-02-24 10:10:50 +03:00
Oğuzhan Koral 2977472c56 Feat (Preferences): Validate model and user preferences 2023-02-22 13:02:18 +03:00
oguzhankoral 8f3a8bfa91 Validate user and model preferences while reading 2023-02-22 12:53:26 +03:00
Oğuzhan Koral 914f6e9516 Fix (Model): Update preferences on state and UI when model has changed 2023-02-21 13:01:53 +03:00
oguzhankoral fa6f65cb7c Use handler methods instead watchers per switch 2023-02-21 12:57:24 +03:00
oguzhankoral dafbe6f995 Update preferences on state and UI when model has changed 2023-02-21 10:47:49 +03:00
Oğuzhan Koral 4a8ac6377b Fix (Preferences): Validate preferences if data is incomplete 2023-02-20 16:47:55 +03:00
oguzhankoral c91fac4ec4 Validate preferences if data is incomplete 2023-02-20 16:30:46 +03:00
Oğuzhan Koral 00d23b084e Chore (Attributes): Send receive attributes as deserialized 2023-02-20 14:58:21 +03:00
oguzhankoral e0a3b82cea Send receive attributes as deserialized 2023-02-20 14:56:01 +03:00
Oğuzhan Koral e49036d8c4 Chore (Attributes): Send and receive ifc classifications 2023-02-20 13:36:52 +03:00
oguzhankoral 4a8356692b Send and receive classifications for component definitions 2023-02-20 13:30:31 +03:00
Oğuzhan Koral adfb7bc63a Fix (BlockDefinition): align detach properties 2023-02-20 12:44:35 +03:00
oguzhankoral 3f41cefa88 Remove basePoint from definition 2023-02-20 12:38:57 +03:00
oguzhankoral 2074ff1987 Rename blockDefinition to definition for BlockInstance 2023-02-20 11:18:50 +03:00
Oğuzhan Koral 65fe189421 Feat (Views): Expand scene support 2023-02-17 22:27:43 +03:00
oguzhankoral bb7590eab4 Simplify sending rendering_options 2023-02-17 22:22:26 +03:00
oguzhankoral 6a04457219 Send and receive rendering options of scenes 2023-02-17 21:51:44 +03:00
oguzhankoral 7f8b1d9586 Include update properties to scenes 2023-02-17 15:39:00 +03:00
52 changed files with 1429 additions and 595 deletions
@@ -2,6 +2,7 @@
require_relative 'event_action'
require_relative '../load_sketchup_model'
require_relative '../collect_preferences'
module SpeckleConnector
module Actions
@@ -18,7 +19,13 @@ module SpeckleConnector
return state unless event_data&.any?
model = event_data.flatten.first
Actions::LoadSketchupModel.update_state(state, model)
# 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
@@ -34,7 +34,10 @@ module SpeckleConnector
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)
return state.with_speckle_state(new_speckle_state.with_invalid_streams_queue)
# 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
@@ -12,8 +12,8 @@ module SpeckleConnector
# 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 event_data [Hash{Symbol=>Array}] the event data grouped by the event name
# @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
@@ -20,7 +20,7 @@ module SpeckleConnector
speckle_state = States::SpeckleState.new(accounts, observers, {}, {})
# This should be the only point that `Sketchup_active_model` passed to application state.
sketchup_state = States::SketchupState.new(Sketchup.active_model)
preferences = Preferences.init_preferences(sketchup_state.sketchup_model)
preferences = Preferences.read_preferences(sketchup_state.sketchup_model)
user_state_with_preferences = state.user_state.with_preferences(preferences)
state = States::State.new(user_state_with_preferences, speckle_state, sketchup_state, false)
Actions::LoadSketchupModel.update_state(state, sketchup_state.sketchup_model)
@@ -2,7 +2,10 @@
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
@@ -15,11 +18,22 @@ module SpeckleConnector
# @param additional_parameters [Array] parameters that the action takes
# @return [States::State] the new updated state object
def self.update_state(state, sketchup_model)
# new_model_state = SketchupModel::Readers::ModelReader.read_model(sketchup_model)
# new_model_state = InitializeMaterials.update_state(new_model_state)
new_sketchup_state = state.sketchup_state.with(:@sketchup_model => 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
@@ -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)
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.user_state.preferences[:model])
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
@@ -18,13 +18,17 @@ module SpeckleConnector
# @return [States::State] the new updated state object
def update_state(state)
state = DeactivateDiffing.update_state(state, {})
converter = Converters::ToSpeckle.new(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,
@stream_id)
state.user_state.preferences)
puts("converted #{base.count} objects for stream #{@stream_id}")
new_state = state.with_speckle_state(new_speckle_state.with_invalid_streams_queue)
# 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 },
@@ -18,6 +18,8 @@ module SpeckleConnector
# @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)
@@ -46,8 +48,19 @@ module SpeckleConnector
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
@@ -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
@@ -6,6 +6,7 @@ module SpeckleConnector
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'
@@ -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
@@ -115,13 +116,9 @@ module SpeckleConnector
# 10. Save object string if detached
@objects[id] = traversed_base if is_detached
if @preferences[:user][:diffing] && !entities.nil?
if @preferences[:user][:register_speckle_entity] && !entities.nil?
entities.uniq.each do |entity|
speckle_entity = if speckle_state.speckle_entities.keys.include?(entity.persistent_id)
speckle_state.speckle_entities[entity.persistent_id].with_valid_stream_id(stream_id)
else
SpeckleEntities.with_converted(entity, traversed_base, stream_id)
end
speckle_entity = create_or_update_speckle_entity(entity, id, traversed_base)
@speckle_state = speckle_state.with_speckle_entity(speckle_entity)
end
end
@@ -345,6 +342,23 @@ module SpeckleConnector
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;
+16 -4
View File
@@ -4,18 +4,30 @@ 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
# @return [String] stream id that conversion happening with it
attr_reader :stream_id
# @return [String] speckle units
attr_reader :units
attr_accessor :definitions
# @param sketchup_state [States::SketchupState] the current sketchup state of the {States::State}
def initialize(sketchup_state)
@sketchup_model = sketchup_state.sketchup_model
su_unit = sketchup_state.length_units
# @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 = {}
end
+215 -100
View File
@@ -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,15 +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 initialize(state)
super(state.sketchup_state)
def from_revit
@from_revit ||= source_app.include?('revit')
end
def can_convert_to_native(obj)
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'])
@@ -49,17 +169,6 @@ module SpeckleConnector
['Objects.BuiltElements.Revit.Parameter'].include?(obj['speckle_type'])
end
# @param obj [Object] speckle commit object.
def receive_commit_object(obj, model_preferences)
# First create layers on the sketchup before starting traversing
filtered_layer_containers = obj.keys.filter_map { |key| key if key.start_with?('@') && key != '@Named Views' }
create_layers(filtered_layer_containers, sketchup_model.layers)
create_views(obj.filter_map { |key, value| value if key == '@Named Views' }, sketchup_model)
# 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, model_preferences)
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.
@@ -76,23 +185,6 @@ module SpeckleConnector
create_folder_layers(folder_layer_arrays, folder)
end
# @param views [Array] views.
# @param sketchup_model [Sketchup::Model] active sketchup model.
def create_views(views, sketchup_model)
return if views.empty?
views.first.each do |view|
origin = view['origin']
target = view['target']
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'], view['lens'])
sketchup_model.active_view.camera = my_camera
sketchup_model.pages.add(view['name'])
end
end
# @param headless_layers [Array<String>] headless layer names.
# @param folder [Sketchup::Layers, Sketchup::LayerFolder] layer folder to create commit layers under it.
def create_headless_layers(headless_layers, folder)
@@ -130,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, model_preferences)
if can_convert_to_native(obj)
convert_to_native(obj, layer, model_preferences)
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)
@@ -142,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, model_preferences)
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, model_preferences)
@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, model_preferences) }
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, model_preferences) }
obj.each { |value| traverse_commit_object(value, commit_folder, layer, entities) }
end
end
# rubocop:enable Metrics/CyclomaticComplexity
@@ -189,82 +281,105 @@ module SpeckleConnector
end
end
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/MethodLength
def convert_to_native(obj, layer, model_preferences, entities = sketchup_model.entities)
convert = method(:convert_to_native)
unless obj['displayValue'].nil?
return display_value_to_native_component(obj, layer, entities, model_preferences, &convert)
end
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,
model_preferences, &convert)
when 'Objects.Other.BlockDefinition' then BLOCK_DEFINITION.to_native(sketchup_model, obj, layer,
obj['name'],
obj['always_face_camera'],
model_preferences,
obj['sketchup_attributes'],
obj['applicationId'],
&convert)
when 'Objects.Geometry.Mesh' then MESH.to_native(sketchup_model, obj, layer, entities, model_preferences)
when 'Objects.Geometry.Brep' then MESH.to_native(sketchup_model, obj['displayValue'], layer, entities,
model_preferences)
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:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/MethodLength
# Creates a component definition and instance from a speckle object with a display value
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/MethodLength
def display_value_to_native_component(obj, layer, entities, model_preferences, &convert)
obj_id = obj['applicationId'].to_s.empty? ? obj['id'] : obj['applicationId']
# rubocop:disable Metrics/PerceivedComplexity
def create_layers_from_categories(state, speckle_object, entities)
return state if speckle_object['category'].nil?
block_definition = obj['@blockDefinition'] || obj['blockDefinition']
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
definition = BLOCK_DEFINITION.to_native(
sketchup_model,
obj['displayValue'],
layer,
"def::#{obj_id}",
if block_definition.nil?
false
else
block_definition['always_face_camera'].nil? ? false : block_definition['always_face_camera']
end,
model_preferences,
if block_definition.nil?
nil
else
block_definition['sketchup_attributes'].nil? ? nil : block_definition['sketchup_attributes']
end,
obj_id,
&convert
)
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
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/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/PerceivedComplexity
# 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 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')
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
# @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
+55 -49
View File
@@ -2,14 +2,13 @@
require_relative 'converter'
require_relative 'base_object_serializer'
require_relative '../relations/many_to_one_relation'
require_relative '../speckle_entities/speckle_entities'
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'
@@ -17,29 +16,10 @@ 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
# @return [States::State] the current speckle state of the {States::State}
attr_reader :state
# @return [States::SpeckleState] the current speckle state of the {States::State}
attr_reader :speckle_state
# @return [Relations::ManyToOneRelation] relations between objects.
attr_accessor :relation
def initialize(state)
super(state.sketchup_state)
@state = state
@speckle_state = @state.speckle_state
@layers = add_all_layers
@relation = Relations::ManyToOneRelation.new
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(preferences)
layers = add_all_layers
state = speckle_state
sketchup_model.selection.each do |entity|
new_speckle_state, converted_object_with_entity = convert(entity, preferences, state)
@@ -55,47 +35,44 @@ module SpeckleConnector
# Add views from pages.
# @param base_object_properties [Hash] dynamically attached base object properties.
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
def add_views(base_object_properties)
views = []
sketchup_model.pages.each do |page|
cam = page.camera
origin = SpeckleObjects::Geometry::Point.new(
SpeckleObjects::Geometry.length_to_speckle(cam.eye[0], @units),
SpeckleObjects::Geometry.length_to_speckle(cam.eye[1], @units),
SpeckleObjects::Geometry.length_to_speckle(cam.eye[2], @units),
@units
)
target = 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
)
direction = 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
)
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
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
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# Get scene properties
# @param page [Sketchup::Page] page on sketchup.
def get_scene_update_properties(page)
{
use_axes: page.use_axes?,
use_camera: page.use_camera?,
use_hidden_geometry: page.use_hidden_geometry?,
use_hidden_layers: page.use_hidden_layers?,
use_hidden_objects: page.use_hidden_objects?,
use_rendering_options: page.use_rendering_options?,
use_section_planes: page.use_section_planes?,
use_shadow_info: page.use_shadow_info?,
use_style: page.use_style?
}
end
# Serialized and traversed information to send batches.
# @param base_and_entity [SpeckleObjects::Base] base object to serialize.
# @param stream_id [String] stream id to send conversion
# @return [String, Integer, Array<Object>] base id, total_children_count of base and batches
def serialize(base_and_entity, speckle_state, preferences, stream_id)
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)
@@ -219,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
@@ -3,6 +3,7 @@
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
@@ -10,112 +11,88 @@ module SpeckleConnector
module Preferences
include Immutable::ImmutableUtils
DICT_HANDLER = SketchupModel::Dictionary::SpeckleModelDictionaryHandler
DEFAULT_CONFIG = "('configSketchup', '{\"dark_theme\":false, \"diffing\":false}');"
DEFAULT_PREFERENCES = '{"dark_theme":false, "diffing":false}'
# 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.
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
def self.init_preferences(sketchup_model)
# Init sqlite database
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
data = db.exec("SELECT content FROM 'objects' WHERE hash = 'configSketchup'")
is_data_empty = data.empty?
is_data_incomplete = !is_data_empty && !JSON.parse(data.first.first).to_h.length != 2
# 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?
# Check configSketchup key is valid or not, otherwise init with default settings
if is_data_empty || is_data_incomplete
db.exec("INSERT INTO 'objects' VALUES #{DEFAULT_CONFIG}") if is_data_empty
if is_data_incomplete
db.exec("UPDATE 'objects' SET content = '#{DEFAULT_PREFERENCES}' WHERE hash = 'configSketchup'")
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
data = db.exec("SELECT content FROM 'objects' WHERE hash = 'configSketchup'").first.first
row_data = database.exec("SELECT content FROM 'objects' WHERE hash = 'configSketchup'").first.first
# Parse string to hash
data_hash = JSON.parse(data).to_h
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']
speckle_dictionary = sketchup_model.attribute_dictionary('Speckle')
if speckle_dictionary
Immutable::Hash.new(
{
user: {
dark_theme: dark_theme,
diffing: diffing
},
model: {
combine_faces_by_material: DICT_HANDLER.get_attribute(
sketchup_model,
:combine_faces_by_material,
'Speckle'
),
include_entity_attributes: DICT_HANDLER.get_attribute(
sketchup_model,
:include_entity_attributes,
'Speckle'
),
include_face_entity_attributes: DICT_HANDLER.get_attribute(
sketchup_model,
:include_face_entity_attributes,
'Speckle'
),
include_edge_entity_attributes: DICT_HANDLER.get_attribute(
sketchup_model,
:include_edge_entity_attributes,
'Speckle'
),
include_group_entity_attributes: DICT_HANDLER.get_attribute(
sketchup_model,
:include_group_entity_attributes,
'Speckle'
),
include_component_entity_attributes: DICT_HANDLER.get_attribute(
sketchup_model,
:include_component_entity_attributes,
'Speckle'
),
merge_coplanar_faces: DICT_HANDLER.get_attribute(
sketchup_model,
:merge_coplanar_faces,
'Speckle'
)
}
}
)
else
DICT_HANDLER.write_initial_model_data(sketchup_model, default_model_preferences)
Immutable::Hash.new(
{
user: {
dark_theme: dark_theme,
diffing: diffing
},
model: default_model_preferences
}
)
end
{
dark_theme: dark_theme,
diffing: diffing,
register_speckle_entity: register_speckle_entity
}.freeze
end
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/AbcSize
def self.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
}
# @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
@@ -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,12 @@ 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 = {}
@@ -16,18 +22,23 @@ module SpeckleConnector
entity.attribute_dictionaries.each do |att_dict|
dict_name = att_dict == '' ? 'empty_dictionary_name' : att_dict.name
dictionaries[dict_name] = att_dict.to_h.to_json unless att_dict.name == 'Speckle_Base_Object'
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
JSON.parse(entries).each do |key, value|
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}")
@@ -35,6 +46,19 @@ module SpeckleConnector
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
@@ -12,13 +12,14 @@ module SpeckleConnector
# 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.
# rubocop:disable Metrics/ParameterLists
def self.write_initial_base_data(sketchup_entity, application_id, id, speckle_type, children_count, stream_id)
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 => id,
APPLICATION_ID => application_id,
SPECKLE_TYPE => speckle_type,
TOTAL_CHILDREN_COUNT => children_count,
TOTAL_CHILDREN_COUNT => children.length,
CHILDREN => children,
VALID_STREAM_IDS => [stream_id],
INVALID_STREAM_IDS => []
}
@@ -1,5 +1,7 @@
# frozen_string_literal: true
require_relative '../../constants/mat_constants'
module SpeckleConnector
# Operations related to {SketchupModel}.
module SketchupModel
@@ -7,6 +9,10 @@ module SpeckleConnector
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
@@ -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,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,23 +0,0 @@
# frozen_string_literal: true
require_relative 'speckle_entity'
require_relative '../immutable/immutable'
module SpeckleConnector
module SpeckleEntities
# Speckle block definition entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
class SpeckleBlockDefinitionEntity < SpeckleEntities::SpeckleEntity
include Immutable::ImmutableUtils
# @return [Hash{String=>SpeckleObjects::Base}] speckle objects belongs to block instance
attr_reader :children
def initialize(sketchup_group_or_component_instance, traversed_speckle_object, stream_id)
@children = traversed_speckle_object[:__closure].nil? ? {} : traversed_speckle_object[:__closure]
super(sketchup_group_or_component_instance, traversed_speckle_object, children, stream_id)
end
alias sketchup_edge sketchup_entity
end
end
end
@@ -1,27 +0,0 @@
# frozen_string_literal: true
require_relative 'speckle_entity'
require_relative '../immutable/immutable'
module SpeckleConnector
module SpeckleEntities
# Speckle block instance entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
class SpeckleBlockInstanceEntity < SpeckleEntities::SpeckleEntity
include Immutable::ImmutableUtils
# @return [Hash{String=>SpeckleObjects::Base}] speckle objects belongs to block instance
attr_reader :children
# @return [Boolean] whether block instance represented as sketchup group or component instance
attr_reader :is_sketchup_group
def initialize(sketchup_group_or_component_instance, traversed_speckle_object, stream_id)
@children = traversed_speckle_object[:__closure].nil? ? {} : traversed_speckle_object[:__closure]
@is_sketchup_group = traversed_speckle_object[:is_sketchup_group]
super(sketchup_group_or_component_instance, traversed_speckle_object, children, stream_id)
end
alias sketchup_edge sketchup_entity
end
end
end
@@ -1,36 +0,0 @@
# frozen_string_literal: true
require_relative 'speckle_entity'
require_relative 'speckle_line_entity'
require_relative 'speckle_mesh_entity'
require_relative 'speckle_block_instance_entity'
require_relative 'speckle_block_definition_entity'
module SpeckleConnector
# Speckle entities are the state holder objects to achieve diffing, caching and updating.
# They are created whenever user send/receive objects between SketchUp and Speckle XYZ server.
# When Sketchup Entity is sent, by checking objects attributes, allow us to understand this object is sent previously?
# If yes, then use it for caching purpose only if object hasn't changed since it's previous sent state.
# If object has sent before but changed after, then update the SpeckleEntity with new traversed object.
# If no, then create SpeckleEntity and add it to the SpeckleState to check later.
module SpeckleEntities
# Speckle entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
def self.with_converted(skp_entity, traversed, stream_id)
# return the same object if it is already SpeckleEntity
return skp_entity if skp_entity.is_a?(SpeckleEntity)
return SpeckleBlockInstanceEntity.new(skp_entity, traversed, stream_id) if skp_entity.is_a?(Sketchup::Group)
if skp_entity.is_a?(Sketchup::ComponentInstance)
return SpeckleBlockInstanceEntity.new(skp_entity, traversed,
stream_id)
end
if skp_entity.is_a?(Sketchup::ComponentDefinition)
return SpeckleBlockDefinitionEntity.new(skp_entity, traversed,
stream_id)
end
return SpeckleMeshEntity.new(skp_entity, traversed, stream_id) if skp_entity.is_a?(Sketchup::Face)
SpeckleLineEntity.new(skp_entity, traversed, stream_id) if skp_entity.is_a?(Sketchup::Edge)
end
end
end
@@ -13,9 +13,6 @@ module SpeckleConnector
# @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 [String] Speckle object type.
attr_reader :speckle_type
@@ -43,27 +40,27 @@ module SpeckleConnector
attr_reader :source_material, :active_diffing_stream_id
# @param sketchup_entity [Sketchup::Entity] sketchup entity represents {SpeckleEntity} on the model.
def initialize(sketchup_entity, traversed_speckle_object, children, stream_id)
# 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 = [stream_id]
@invalid_stream_ids = []
@valid_stream_ids = valid_stream_ids
@invalid_stream_ids = invalid_stream_ids
@sketchup_entity = sketchup_entity
@application_id = @sketchup_entity.persistent_id
@id = traversed_speckle_object[:id]
@total_children_count = traversed_speckle_object[:totalChildrenCount]
@speckle_object = traversed_speckle_object
@speckle_type = speckle_object[:speckle_type]
@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.length, stream_id)
# FIXME: Understand why below condition does not match for same cases. I guess it is a typo bug.
# unless total_children_count == speckle_children_objects.length
# raise StandardError "total children count mismatch for #{application_id}"
# end
@speckle_children_objects, valid_stream_ids.first)
end
def with_up_to_date
@@ -1,23 +0,0 @@
# frozen_string_literal: true
require_relative 'speckle_entity'
require_relative '../immutable/immutable'
module SpeckleConnector
module SpeckleEntities
# Speckle line entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
class SpeckleLineEntity < SpeckleEntities::SpeckleEntity
include Immutable::ImmutableUtils
# @return [Hash{String=>SpeckleObjects::Base}] speckle objects belongs to edge
attr_reader :children
def initialize(sketchup_edge, traversed_speckle_object, stream_id)
@children = traversed_speckle_object[:__closure].nil? ? {} : traversed_speckle_object[:__closure]
super(sketchup_edge, traversed_speckle_object, children, stream_id)
end
alias sketchup_edge sketchup_entity
end
end
end
@@ -1,23 +0,0 @@
# frozen_string_literal: true
require_relative 'speckle_entity'
require_relative '../immutable/immutable'
module SpeckleConnector
module SpeckleEntities
# Speckle mesh entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
class SpeckleMeshEntity < SpeckleEntities::SpeckleEntity
include Immutable::ImmutableUtils
# @return [Hash{String=>SpeckleObjects::Base}] speckle objects belongs to edge
attr_reader :children
def initialize(sketchup_face, traversed_speckle_object, stream_id)
@children = traversed_speckle_object[:__closure].nil? ? {} : traversed_speckle_object[:__closure]
super(sketchup_face, traversed_speckle_object, children, stream_id)
end
alias sketchup_edge sketchup_entity
end
end
end
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative '../base'
require_relative '../../constants/type_constants'
require_relative '../../speckle_objects/geometry/point'
require_relative '../../speckle_objects/geometry/vector'
@@ -9,7 +10,7 @@ module SpeckleConnector
module BuiltElements
# View3d object represents scenes on Sketchup.
class View3d < Base
SPECKLE_TYPE = 'Objects.BuiltElements.View:Objects.BuiltElements.View3D'
SPECKLE_TYPE = OBJECTS_BUILTELEMENTS_VIEW3D
# @param name [String] name of the scene
# @param origin [SpeckleObjects::Geometry::Point] origin (eye) of the view.
@@ -20,9 +21,11 @@ module SpeckleConnector
# @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)
is_perspective, lens, units, application_id, update_properties, rendering_options)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
@@ -37,8 +40,79 @@ module SpeckleConnector
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
@@ -59,11 +59,12 @@ module SpeckleConnector
)
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']) }
@@ -81,6 +82,7 @@ module SpeckleConnector
.attribute_dictionaries_to_native(edge, line['sketchup_attributes']['dictionaries'])
end
end
return state, edges
end
# rubocop:enable Metrics/AbcSize
@@ -5,6 +5,7 @@ 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
@@ -48,13 +49,34 @@ module SpeckleConnector
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
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/CyclomaticComplexity
def self.to_native(sketchup_model, mesh, layer, entities, model_preferences)
# 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)
@@ -66,25 +88,45 @@ 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 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) if model_preferences[:merge_coplanar_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
# rubocop:disable Style/MultilineTernaryOperator
@@ -185,12 +227,23 @@ module SpeckleConnector
end
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']
DEFINITIONS_WILL_BE_HARD_EDGE = %w[
Walls
Floors
Stairs
Structural Foundations
Doors
Windows
].freeze
# @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
return DEFINITIONS_WILL_BE_HARD_EDGE.none? { |def_name| entities.parent.name.include?(def_name) }
end
def self.get_native_points(mesh)
@@ -17,12 +17,11 @@ 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.
# rubocop:disable Metrics/ParameterLists
def initialize(geometry:, base_point:, name:, units:, always_face_camera:, sketchup_attributes: {},
def initialize(geometry:, name:, units:, always_face_camera:, sketchup_attributes: {},
application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
@@ -32,7 +31,6 @@ module SpeckleConnector
)
self[:units] = units
self[:name] = name
self[:basePoint] = base_point
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.
@@ -89,7 +87,6 @@ module SpeckleConnector
block_definition = BlockDefinition.new(
units: units,
name: definition.name,
base_point: Geometry::Point.new(0, 0, 0, units),
geometry: geometry,
always_face_camera: definition.behavior.always_face_camera?,
sketchup_attributes: att,
@@ -103,24 +100,45 @@ module SpeckleConnector
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/ParameterLists
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 sketchup_model [Sketchup::Model] sketchup model to check block definitions.
# @param state [States::State] state of the application.
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/ParameterLists
def self.to_native(sketchup_model, geometry, layer, name, always_face_camera, model_preferences,
sketchup_attributes, application_id = '', &convert)
definition = sketchup_model.definitions[name]
return definition if definition && (definition.name == name || definition.guid == application_id)
# 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
definition&.entities&.clear!
definition ||= sketchup_model.definitions.add(name)
definition.layer = layer
if geometry.is_a?(Array)
geometry.each { |obj| convert.call(obj, layer, model_preferences, definition.entities) }
# 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
if geometry.is_a?(Hash) && !geometry['speckle_type'].nil?
convert.call(geometry, layer, model_preferences, definition.entities)
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}")
@@ -129,11 +147,12 @@ module SpeckleConnector
SketchupModel::Dictionary::DictionaryHandler
.attribute_dictionaries_to_native(definition, sketchup_attributes['dictionaries'])
end
definition
return state, [definition]
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/ParameterLists
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/AbcSize
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
@@ -38,7 +38,7 @@ module SpeckleConnector
self[:transform] = transform
self[:sketchup_attributes] = sketchup_attributes if sketchup_attributes.any?
# FIXME: Since blockDefinition sends with @ as detached, block basePlane renders on viewer.
self['@@blockDefinition'] = block_definition
self['@@definition'] = block_definition
end
# rubocop:enable Metrics/ParameterLists
@@ -104,37 +104,67 @@ module SpeckleConnector
# 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
# rubocop:disable Metrics/ParameterLists
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def self.to_native(sketchup_model, block, layer, entities, model_preferences, &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
# is_group = block['is_sketchup_group']
block_definition = block['@blockDefinition'] || block['blockDefinition']
geometry = block_definition['@geometry'] || block_definition['geometry']
definition = BlockDefinition.to_native(
sketchup_model,
geometry,
# NOTE: nil checks for backward compatibility
block_definition = block['definition'] || block['blockDefinition'] || block['@blockDefinition']
state, _definitions = BlockDefinition.to_native(
state,
block_definition,
layer,
block_definition['name'],
block_definition['always_face_camera'].nil? ? false : block_definition['always_face_camera'],
model_preferences,
block_definition['sketchup_attributes'],
block_definition['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']
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
# takes a component definition and finds and erases the first instance with the matching name
# (and optionally the applicationId)
# 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 }
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
@@ -150,28 +180,32 @@ module SpeckleConnector
# 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'])
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
instance.material = Other::RenderMaterial.to_native(sketchup_model, block['renderMaterial'])
instance.name = instance_name
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
instance
return state, [instance, definition]
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/ParameterLists
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# 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: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
@@ -49,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)
@@ -2,6 +2,7 @@
require_relative '../immutable/immutable'
require_relative '../sketchup_model/materials/materials'
require_relative '../sketchup_model/definitions/definitions'
module SpeckleConnector
module States
@@ -14,10 +15,14 @@ module SpeckleConnector
# @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.
@@ -25,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
@@ -54,6 +54,12 @@ module SpeckleConnector
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
+4
View File
@@ -45,6 +45,10 @@ module SpeckleConnector
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
@@ -15,6 +15,14 @@ module SpeckleConnector
@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
+10 -4
View File
@@ -7,8 +7,8 @@
>
<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-triangle-outline</v-icon>
<v-icon v-else class="toggleUpDown" :class='{ "rotate": diffing }' small>mdi-triangle</v-icon>
<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 :) -->
@@ -161,6 +161,7 @@ import gql from 'graphql-tag'
import { bus } from '../main'
import streamQuery from '../graphql/stream.gql'
import ObjectLoader from '@speckle/objectloader'
import {HostApplications} from '@/utils/hostApplications'
global.convertedFromSketchup = function (streamId, batches, commitId, totalChildrenCount) {
bus.$emit(`sketchup-objects-${streamId}`, batches, commitId, totalChildrenCount)
@@ -376,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
@@ -398,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({
@@ -111,7 +111,7 @@ export default {
this.showCreateBranch = false
this.branchName = ""
this.description = ""
this.$mixpanel.track('Connector Action', { name: 'Create Branch' })
this.$mixpanel.track('Connector Action', { name: 'Branch Create' })
return res
}
}
@@ -246,6 +246,7 @@ export default {
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', {
@@ -276,7 +277,7 @@ export default {
this.showCreateNewStream = false
this.streamName = ""
this.description = ""
this.$mixpanel.track('Connector Action', { name: 'Create Stream' })
this.$mixpanel.track('Connector Action', { name: 'Stream Create' })
sketchup.exec({name: "save_stream", data: {stream_id: res["data"]["streamCreate"]}})
this.refresh()
return res
+117 -98
View File
@@ -24,62 +24,74 @@
</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
v-model="diffing"
:input-value="diffing"
class="pt-3 mt-n2 mb-n2"
:label="'Diffing (Alpha)'"
@click="switchDiffing"
@change="diffingHandler"
/>
<div class="sm1 mt-3">Send Strategy</div>
<div class="sm1 mt-3">Model Preferences</div>
<v-divider class="mb-2"/>
<v-switch
v-model="combineFacesByMaterial"
class="pt-1 mt-n2 mb-n2"
:input-value="combineFacesByMaterial"
:label="'Combine faces by material under mesh'"
@change="combineFacesByMaterialHandler"
/>
<v-switch
v-model="includeAttributes"
: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
v-model="includeEdgeAttributes"
: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
v-model="includeFaceAttributes"
: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
v-model="includeGroupAttributes"
: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
v-model="includeComponentAttributes"
:input-value="includeComponentAttributes"
class="pt-1 my-n5 ml-10"
:label="'Component'"
:disabled="!includeAttributes"
@change="includeComponentAttributesHandler"
/>
<div class="sm1 mt-3">Receive Strategy</div>
<v-divider class="mb-2"/>
<v-switch
v-model="mergeCoplanarFaces"
:input-value="mergeCoplanarFaces"
class="pt-1 mt-n2 mb-n2"
:label="'Merge co-planar faces on receive'"
@change="mergeCoplanarFacesHandler"
/>
</v-container>
<v-card-actions>
@@ -120,100 +132,107 @@ export default {
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
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: 'Open Settings Dialog' })
this.$mixpanel.track('Connector Action', { name: 'Settings Open' })
}
},
deep: true
},
'combineFacesByMaterial': {
handler(newValue) {
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "combine_faces_by_material", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Combine Faces By Material Option' })
},
deep: true
},
'includeAttributes': {
handler(newValue) {
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "include_entity_attributes", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Include Entity Attributes Option' })
},
deep: true
},
'includeFaceAttributes': {
handler(newValue) {
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "include_face_entity_attributes", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Include Face Entity Attributes Option' })
},
deep: true
},
'includeEdgeAttributes': {
handler(newValue) {
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "include_edge_entity_attributes", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Include Edge Entity Attributes Option' })
},
deep: true
},
'includeGroupAttributes': {
handler(newValue) {
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "include_group_entity_attributes", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Include Group Entity Attributes Option' })
},
deep: true
},
'includeComponentAttributes': {
handler(newValue) {
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "include_component_entity_attributes", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Include Component Entity Attributes Option' })
},
deep: true
},
'mergeCoplanarFaces': {
handler(newValue) {
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "merge_coplanar_faces", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Merge Co-Planar Faces Option' })
},
deep: true
},
'diffing': {
handler(newValue) {
sketchup.exec({
name: "user_preferences_updated",
data: {preference_hash: "configSketchup", preference: "diffing", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Diffing' })
},
deep: true
}
}
},
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({
+1
View File
@@ -42,6 +42,7 @@ query Stream($id: String!) {
branchName
authorName
authorAvatar
sourceApplication
referencedObject
}
}
+86
View File
@@ -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;
}
}