Compare commits

...

53 Commits

Author SHA1 Message Date
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
Oğuzhan Koral 4362cc53bb Feat (Diffing): MVP for continuous traversal
Fixed issues:
- Origin Point of Components Visible in the Viewer
- Align detach properties with other connectors
- MVP for continuous traversal
- Create SpeckleEntity object as state holder
2023-02-17 02:26:47 +03:00
oguzhankoral 959e6b2a12 Fix rubocop issues 2023-02-17 02:23:27 +03:00
oguzhankoral 2d91070e5a Distint detached properties and dynamically detached properties 2023-02-17 02:17:00 +03:00
oguzhankoral cc038ece32 Remove bbox from mesh 2023-02-17 02:16:59 +03:00
oguzhankoral 7af1292f29 Add setting for diffing 2023-02-17 02:16:59 +03:00
oguzhankoral 2c3fd7a84f Update preferences if empty or has incomplete data 2023-02-17 02:16:59 +03:00
oguzhankoral d36f70dbc7 Align base object serializer with continuous traversal 2023-02-17 02:16:59 +03:00
oguzhankoral 77f5f29c90 Rename batch logging method 2023-02-17 02:16:58 +03:00
oguzhankoral ead45ed843 Remove applicationId from clean base object before traverse props 2023-02-17 02:16:58 +03:00
oguzhankoral a6259bb7eb Print on Speckle folder 2023-02-17 02:16:58 +03:00
oguzhankoral 0249ebeb95 Move is_detached to old order 2023-02-17 02:16:57 +03:00
oguzhankoral 1c3a35a137 Fill delta icon if diffing active on stream 2023-02-17 02:16:57 +03:00
oguzhankoral c12319b417 Activate and deactivate diffing for streams safely 2023-02-17 02:16:57 +03:00
oguzhankoral 90ac00a628 Pass stream if to speckle entities 2023-02-17 02:16:57 +03:00
oguzhankoral e5c71cb3a4 Handle speckle entity dictionaries according validation of object 2023-02-17 02:16:56 +03:00
oguzhankoral ebf0681574 Collect invalid streams from speckle_state 2023-02-17 02:16:56 +03:00
oguzhankoral 642643f412 Remove unnecssary implementation for entity utis 2023-02-17 02:16:56 +03:00
oguzhankoral 8dd65ac255 Init diffing materials with plugin initialization 2023-02-17 02:16:55 +03:00
oguzhankoral 8df58f226b Invalida speckle entities when they edited 2023-02-17 02:16:55 +03:00
oguzhankoral 58b4f2ce14 Disable observers on commands 2023-02-17 02:16:55 +03:00
oguzhankoral 65f0c836fe Align objects with continuous traversal 2023-02-17 02:16:55 +03:00
oguzhankoral 2aa16085a5 Return converted entities regardless 2023-02-17 02:16:54 +03:00
oguzhankoral 25f05a69c6 Return new speckle_state from serializer 2023-02-17 02:16:54 +03:00
oguzhankoral 5c2a94da16 Align speckle entities with traversed objects 2023-02-17 02:16:54 +03:00
oguzhankoral 162325e90e Add desktop path to constants 2023-02-17 02:16:54 +03:00
oguzhankoral b022a8c608 Implement observers 2023-02-17 02:16:53 +03:00
oguzhankoral 1e404f5e6b Return converted and traversed versions from entities 2023-02-17 02:16:53 +03:00
oguzhankoral 8ef7780332 Write application_id to dictionary of SpeckleEntity 2023-02-17 02:16:53 +03:00
oguzhankoral 51b4f7b3f7 Use definition persistent id for nested block definitions 2023-02-17 02:16:53 +03:00
oguzhankoral 9b3fa33e50 Save traversed object details into SpeckleEntity's dictionary 2023-02-17 02:16:52 +03:00
oguzhankoral 9349d0813d Implement speckle_block_instance_entity 2023-02-17 02:16:52 +03:00
oguzhankoral 8d9bc500a1 Write traversed info to speckle entity's dictionary 2023-02-17 02:16:52 +03:00
oguzhankoral cae7b6e29f Add line and mesh speckle entities 2023-02-17 02:16:51 +03:00
oguzhankoral 0b4ac732b2 Fix traversal missing references 2023-02-17 02:16:51 +03:00
oguzhankoral acacbb91e3 Add single unit test for line traversing 2023-02-17 02:16:51 +03:00
oguzhankoral fd6d3d9a2f Pass state to converters 2023-02-17 02:16:51 +03:00
oguzhankoral 3b531e30b1 Disable caching temporarly 2023-02-17 02:16:50 +03:00
oguzhankoral 0c78085b2e Remove command data from console logging 2023-02-17 02:16:50 +03:00
oguzhankoral 4ab53308f7 Initialize relations on to_speckle 2023-02-17 02:16:50 +03:00
oguzhankoral 06ae161793 Check base object already traversed or not 2023-02-17 02:16:50 +03:00
oguzhankoral 94e005d2f8 Fix speckle_state immutable update on state 2023-02-17 02:16:49 +03:00
oguzhankoral 717072c3a5 Introduce speckle_entity objects 2023-02-17 02:16:49 +03:00
oguzhankoral 02b4bde92a Add speckle state for serialization process 2023-02-17 02:16:49 +03:00
oguzhankoral a397d1e233 Return speckle state from traversal methods 2023-02-17 02:16:45 +03:00
64 changed files with 1774 additions and 305 deletions
@@ -0,0 +1,33 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'deactivate_diffing'
module SpeckleConnector
module Actions
# Deactivate diffing for stream.
class ActivateDiffing < Action
def initialize(stream_id)
super()
@stream_id = stream_id
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
state = DeactivateDiffing.update_state(state, {})
puts "Diffing activated for #{@stream_id}"
speckle_entities = state.speckle_state.speckle_entities
invalid_speckle_entities = speckle_entities.select do |_id, entity|
entity.invalid_stream_ids.include?(@stream_id) && entity.sketchup_entity.is_a?(Sketchup::Face)
end
invalid_speckle_entities.each do |id, entity|
new_entity = entity.activate_diffing(@stream_id, state.sketchup_state.materials.by_id(MAT_EDIT))
speckle_entities = speckle_entities.put(id, new_entity)
end
new_speckle_state = state.speckle_state.with_speckle_entities(speckle_entities)
state.with_speckle_state(new_speckle_state)
end
end
end
end
@@ -0,0 +1,52 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Adds material to speckle state and Sketchup.
class AddMaterial < Action
def self.update_state(state, material_name:, color:, material_id:, alpha: nil)
materials = state.sketchup_state.materials
existing_material = materials.by_id(material_id)
return state if existing_material&.valid?
new_material = create_or_get_material(state.sketchup_state.sketchup_model,
material_name,
color,
material_id,
alpha: alpha)
new_materials = materials.add_material(material_id, new_material)
new_sketchup_state = state.sketchup_state.with(:@materials => new_materials)
state.with(:@sketchup_state => new_sketchup_state)
end
def self.create_or_get_material(model, material_name, color, material_id, alpha: nil)
materials = model.materials
existing_material = materials.find { |mat| mat.name == material_name }
return existing_material if existing_material&.valid?
existing_material = materials.add material_name
existing_material.set_attribute(MAT_DICTIONARY, MAT_ID, material_id.to_s)
set_hex_color(existing_material, color)
existing_material.alpha = alpha if alpha
existing_material
end
def self.set_hex_color(skp_material, hex_value)
hex_value = hex_value.to_s
col_blue, col_green, col_red = parse_hex_color(hex_value)
skp_material.color = col_red, col_green, col_blue
end
def self.parse_hex_color(hex_value)
split_values = hex_value.match(/^#([a-fA-F\d]{2})([a-fA-F\d]{2})([a-fA-F\d]{2})$/) ||
hex_value.match(/^#([a-fA-F\d])([a-fA-F\d])([a-fA-F\d])$/)
col_red = split_values[1].hex
col_green = split_values[2].hex
col_blue = split_values[3].hex
return col_blue, col_green, col_red
end
end
end
end
@@ -0,0 +1,26 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Deactivate diffing.
class DeactivateDiffing < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
puts 'Diffing deactivated!'
speckle_entities = state.speckle_state.speckle_entities
diffing_activated_speckle_entities = speckle_entities.reject do |_id, entity|
entity.active_diffing_stream_id.nil?
end
diffing_activated_speckle_entities.each do |id, entity|
new_entity = entity.deactivate_diffing
speckle_entities = speckle_entities.put(id, new_entity)
end
new_speckle_state = state.speckle_state.with_speckle_entities(speckle_entities)
state.with_speckle_state(new_speckle_state)
end
end
end
end
@@ -0,0 +1,49 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../load_sketchup_model'
module SpeckleConnector
module Actions
module Events
# Handle events that are triggered by the {AppObserver}.
class AppEventAction < EventAction
# Handle loading new or existing model
class OnNewOrChangedModel
# Handle events when the new or existing model is loaded in Sketchup
# @param state [States::State] the current state of speckle application
# @param event_data [Array<(Sketchup::Model)>] the event data for the given event. It consists of
# a double array with a single element that is the {Sketchup::Model} object of the loaded model.
def self.update_state(state, event_data)
return state unless event_data&.any?
model = event_data.flatten.first
Actions::LoadSketchupModel.update_state(state, model)
end
end
# Run actions that are needed before the sketchup quits
class OnQuit
# Handle when Sketchup application closes
# @param state [States::State] the current state of speckle application
# @param _event_data [Array] the event data
# @return [States::State] the transformed state object
def self.update_state(state, _event_data)
state
end
end
# Handlers that are used to handle specific events
ACTIONS = {
onNewModel: OnNewOrChangedModel,
onOpenModel: OnNewOrChangedModel,
onQuit: OnQuit
}.freeze
def self.actions
ACTIONS
end
end
end
end
end
@@ -0,0 +1,81 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../../sketchup_model/utils/face_utils'
require_relative '../../constants/dict_constants'
module SpeckleConnector
module Actions
module Events
# Event actions related to entities.
class EntitiesEventAction < EventAction
# Event action when element added.
class OnElementAdded
# @param state [States::State] the current state of the SpeckleConnector Application
def self.update_state(state, event_data)
modified_entities = event_data.to_a.collect { |e| e[1] }
# do not copy speckle base object specific attributes, because they are entity specific
modified_entities.each { |entity| entity.delete_attribute(SPECKLE_BASE_OBJECT) }
state
end
end
# Event action when element modified.
class OnElementModified
# @param state [States::State] the current state of the SpeckleConnector Application
def self.update_state(state, event_data)
speckle_state = state.speckle_state
modified_entity = event_data[0][1]
if modified_entity.is_a?(Sketchup::Face)
path = state.sketchup_state.sketchup_model.active_path
modified_faces = SketchupModel::Utils::FaceUtils.near_faces(modified_entity.edges)
path_objects = path.nil? ? [] : path + path.collect(&:definition)
parent_ids = path_objects.collect(&:persistent_id)
ids_to_invalidate = modified_faces.collect(&:persistent_id) + parent_ids
entities_to_invalidate = speckle_entities_to_invalidate(speckle_state, ids_to_invalidate)
new_speckle_state = invalidate_speckle_entities(speckle_state, entities_to_invalidate)
return state.with_speckle_state(new_speckle_state.with_invalid_streams_queue)
end
state
end
# @param speckle_state [States::SpeckleState] the current state of the Speckle
def self.speckle_entities_to_invalidate(speckle_state, ids)
speckle_state.speckle_entities.to_h.select { |id, _| ids.include?(id) }
end
# @param speckle_state [States::SpeckleState] the current state of the Speckle
def self.invalidate_speckle_entities(speckle_state, entities_to_invalidate)
speckle_entities = speckle_state.speckle_entities
entities_to_invalidate.each do |id, speckle_entity|
edited_speckle_entity = speckle_entity.with_invalid
speckle_entities = speckle_entities.put(id, edited_speckle_entity)
end
speckle_state.with_speckle_entities(speckle_entities)
end
end
# Event action when element removed.
class OnElementRemoved
# @param state [States::State] the current state of the SpeckleConnector Application
def self.update_state(state, _event_data)
# TODO: Do state updates when element removed
state
end
end
# Handlers that are used to handle specific events
ACTIONS = {
onElementRemoved: OnElementRemoved,
onElementAdded: OnElementAdded,
onElementModified: OnElementModified
}.freeze
def self.actions
ACTIONS
end
end
end
end
end
@@ -0,0 +1,34 @@
# frozen_string_literal: true
module SpeckleConnector
module Actions
# This module contains actions that are performed to handle events triggered by observers in Sketchup.
module Events
# Base action for Handling events
class EventAction
def self.actions
raise NoMethodError, 'Implement in a subclass'
end
# Handle the events that were collected by the observer. In case of the selection observer,
# we only need to handle the events once if any of the events actually happened.
# @param event_data [Hash{Symbol=>Array}] the event data grouped by the event name
# @param state [States::State] the current state of the SpeckleConnector Application
# @return [States::State] the transformed state
def self.update_state(state, events)
# Don't do anything if there are no events for this action
return state unless events
actions = self.actions
actions.each do |event_name, action|
next unless events.key?(event_name)
event_data = events[event_name]
state = action.update_state(state, event_data)
end
state
end
end
end
end
end
@@ -0,0 +1,41 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../load_sketchup_model'
module SpeckleConnector
module Actions
module Events
# Handle events that are triggered by the {ModelObserver}.
class ModelEventAction < EventAction
# Handle loading new or existing model
class OnActivePathChanged
# Handle events when the new or existing model is loaded in Sketchup
# @param state [States::State] the current state of speckle application
# @param event_data [Array<(Sketchup::Model)>] the event data for the given event. It consists of
# a double array with a single element that is the {Sketchup::Model} object of the loaded model.
def self.update_state(state, _event_data)
sketchup_state = state.sketchup_state
active_path = sketchup_state.sketchup_model.active_path
observers = state.speckle_state.observers
update_object_observers(active_path, observers)
return state
end
def self.update_object_observers(path, observers)
path[-1].definition.entities.add_observer(observers[ENTITIES_OBSERVER]) unless path.nil?
end
end
# Handlers that are used to handle specific events
ACTIONS = {
onActivePathChanged: OnActivePathChanged
}.freeze
def self.actions
ACTIONS
end
end
end
end
end
@@ -0,0 +1,30 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'add_material'
require_relative '../constants/mat_constants'
module SpeckleConnector
module Actions
# Action to initialize materials
class InitializeMaterials < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state)
new_state = recreate_material(state, DEFAULT_NAMES[MAT_ADD], DEFAULT_COLORS[MAT_ADD], MAT_ADD)
new_state = recreate_material(new_state, DEFAULT_NAMES[MAT_EDIT], DEFAULT_COLORS[MAT_EDIT], MAT_EDIT)
recreate_material(new_state, DEFAULT_NAMES[MAT_REMOVE], DEFAULT_COLORS[MAT_REMOVE], MAT_REMOVE)
end
def self.recreate_material(state, name, color, id, alpha: nil)
Actions::AddMaterial.update_state(
state,
material_name: name,
color: color,
material_id: id,
alpha: alpha
)
end
end
end
end
@@ -6,6 +6,7 @@ require_relative '../states/speckle_state'
require_relative '../states/sketchup_state'
require_relative '../accounts/accounts'
require_relative '../preferences/preferences'
require_relative '../constants/observer_constants'
module SpeckleConnector
module Actions
@@ -13,14 +14,20 @@ module SpeckleConnector
class InitializeSpeckle < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state)
def self.update_state(state, observers)
attach_app_observer!(observers[APP_OBSERVER])
accounts = SpeckleConnector::Accounts.load_accounts
speckle_state = States::SpeckleState.new(accounts, {}, {})
speckle_state = States::SpeckleState.new(accounts, observers, {}, {})
# This should be the only point that `Sketchup_active_model` passed to application state.
sketchup_state = States::SketchupState.new(Sketchup.active_model)
preferences = Preferences.init_preferences(sketchup_state.sketchup_model)
user_state_with_preferences = state.user_state.with_preferences(preferences)
States::State.new(user_state_with_preferences, speckle_state, sketchup_state, false)
state = States::State.new(user_state_with_preferences, speckle_state, sketchup_state, false)
Actions::LoadSketchupModel.update_state(state, sketchup_state.sketchup_model)
end
def self.attach_app_observer!(observer)
Sketchup.add_observer(observer)
end
end
end
@@ -0,0 +1,45 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'initialize_materials'
require_relative '../states/state'
require_relative '../constants/observer_constants'
module SpeckleConnector
module Actions
# Switch sketchup model wit a new one
class LoadSketchupModel < Action
# Replace current model state with the state of a new model. This action is triggered when user opens new or
# existing Sketchup model.
# @param state [States::State] the current state of Speckle
# @param additional_parameters [Array] parameters that the action takes
# @return [States::State] the new updated state object
def self.update_state(state, sketchup_model)
# 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)
new_state = state.with(:@sketchup_state => new_sketchup_state)
new_state = InitializeMaterials.update_state(new_state)
attach_observers(sketchup_model, new_state.speckle_state.observers)
new_state
end
# Attach observers to the sketchup model
# @param sketchup_model [Sketchup::Model] the model to attach observers to
# @param observers [Hash{Class=>}] the observer objects indexed by their class that will be attached
def self.attach_observers(sketchup_model, observers)
# selection = sketchup_model.selection
# selection.add_observer(observers[SELECTION_OBSERVER_NAME])
# layers = sketchup_model.layers
# layers.add_observer(observers[LAYERS_OBSERVER_NAME])
entities = sketchup_model.entities
entities.add_observer(observers[ENTITIES_OBSERVER])
sketchup_model.add_observer(observers[MODEL_OBSERVER])
# materials = sketchup_model.materials
# materials.add_observer(observers[MATERIALS_OBSERVER_NAME])
# pages = sketchup_model.pages
# pages.add_observer(observers[PAGES_OBSERVER_NAME])
end
end
end
end
@@ -0,0 +1,34 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'events/app_event_action'
require_relative 'events/entities_event_action'
require_relative 'events/model_event_action'
require_relative '../constants/observer_constants'
module SpeckleConnector
module Actions
# Handle events that were collected by observers
class OnEventsAction < Action
RUN_ORDER = {
APP_OBSERVER => Events::AppEventAction,
ENTITIES_OBSERVER => Events::EntitiesEventAction,
MODEL_OBSERVER => Events::ModelEventAction
# MATERIALS_OBSERVER => Events::MaterialsEventAction,
# LAYERS_OBSERVER => Events::LayerEventAction,
# PAGES_OBSERVER => Events::PagesEventAction,
# SELECTION_OBSERVER => Events::SelectionEventAction
}.freeze
def self.update_state(state, events)
RUN_ORDER.each do |observer_name, action|
next unless events.key?(observer_name)
parameters = events[observer_name]
state = action.update_state(state, parameters)
end
state
end
end
end
end
@@ -20,7 +20,7 @@ module SpeckleConnector
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
converter = Converters::ToNative.new(state.sketchup_state.sketchup_model)
converter = Converters::ToNative.new(state)
# 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])
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'deactivate_diffing'
require_relative '../convertors/units'
require_relative '../convertors/to_speckle'
@@ -16,16 +17,19 @@ module SpeckleConnector
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
sketchup_model = state.sketchup_state.sketchup_model
converter = Converters::ToSpeckle.new(sketchup_model)
base = converter.convert_selection_to_base(state.user_state.preferences)
id, total_children_count, batches = converter.send_info(base)
state = DeactivateDiffing.update_state(state, {})
converter = Converters::ToSpeckle.new(state)
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)
puts("converted #{base.count} objects for stream #{@stream_id}")
state.with_add_queue('convertedFromSketchup', @stream_id, [
{ is_string: false, val: batches },
{ is_string: true, val: id },
{ is_string: false, val: total_children_count }
])
new_state = state.with_speckle_state(new_speckle_state.with_invalid_streams_queue)
new_state.with_add_queue('convertedFromSketchup', @stream_id, [
{ is_string: false, val: batches },
{ is_string: true, val: id },
{ is_string: false, val: total_children_count }
])
end
end
end
@@ -15,6 +15,9 @@ module SpeckleConnector
# @return [Ui::UiController] controller for ui views
attr_reader :ui_controller
# @return [Observers::Handler] the observers indexed by their classes to handle
attr_reader :observer_handler
def initialize(menu_commands, state, ui_controller)
@menu_commands = menu_commands
@state = state
@@ -35,6 +38,10 @@ module SpeckleConnector
update_state!(Actions::ClearQueue)
end
def add_observer_handler!(observer_handler)
@observer_handler = observer_handler
end
def update_state!(action, *parameters)
old_state = @state
@state = action.update_state(old_state, *parameters)
@@ -0,0 +1,17 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/activate_diffing'
module SpeckleConnector
module Commands
# Command to activate diffing for stream.
class ActivateDiffing < Command
def _run(data)
stream_id = data['stream_id']
action = Actions::ActivateDiffing.new(stream_id)
app.update_state!(action)
end
end
end
end
+12 -1
View File
@@ -18,11 +18,22 @@ module SpeckleConnector
def run(*parameters)
# Run here common operations that same for each command.
_run(*parameters)
with_observers_disabled do
_run(*parameters)
end
end
private
def with_observers_disabled(&block)
observer_handler = @app.observer_handler
if observer_handler
observer_handler.with_observers_disabled(&block)
else
block.call
end
end
def _run(*_parameters)
raise NotImplementedError, 'Implement in subclass'
end
@@ -4,6 +4,7 @@ require_relative 'command'
require_relative '../states/initial_state'
require_relative '../ui/vue_view'
require_relative '../actions/initialize_speckle'
require_relative '../observers/factory'
module SpeckleConnector
module Commands
@@ -30,7 +31,10 @@ module SpeckleConnector
# Do the actual Speckle initialization.
def initialize_speckle(app)
# TODO: Initialize here speckle states and observers.
app.update_state!(Actions::InitializeSpeckle)
observer_handler = Observers::Factory.create_handler(app)
app.add_observer_handler!(observer_handler)
observers = Observers::Factory.create_observers(observer_handler)
app.update_state!(Actions::InitializeSpeckle, observers)
dialog_specs = {
dialog_id: Ui::SPECKLE_UI_ID,
htm_file: Ui::VUE_UI_HTML,
@@ -1,8 +1,12 @@
# frozen_string_literal: true
module SpeckleConnector
SPECKLE_BASE_OBJECT = 'Speckle_Base_Object'
SPECKLE_ID = 'speckle_id'
SPECKLE_TYPE = 'speckle_type'
APPLICATION_ID = 'application_id'
TOTAL_CHILDREN_COUNT = 'total_children_count'
PARENT = 'parent'
VALID_STREAM_IDS = 'valid_stream_ids'
INVALID_STREAM_IDS = 'invalid_stream_ids'
end
@@ -0,0 +1,22 @@
# frozen_string_literal: true
module SpeckleConnector
MAT_DICTIONARY = 'Speckle_Connector_Materials'
MAT_ID = 'Speckle_Connector_Material_Id'
MAT_ADD = :speckle_connector_add_material
MAT_EDIT = :speckle_connector_edit_material
MAT_REMOVE = :speckle_connector_remove_material
DEFAULT_COLORS = {
MAT_ADD => '#66FF66',
MAT_EDIT => '#FFFF9F',
MAT_REMOVE => '#FF6666'
}.freeze
DEFAULT_NAMES = {
MAT_ADD => 'Speckle_Material_Add',
MAT_EDIT => 'Speckle_Material_Edit',
MAT_REMOVE => 'Speckle_Material_Remove'
}.freeze
end
@@ -0,0 +1,7 @@
# frozen_string_literal: true
module SpeckleConnector
APP_OBSERVER = 'SpeckleConnector::Observers::AppObserver'
ENTITIES_OBSERVER = 'SpeckleConnector::Observers::EntitiesObserver'
MODEL_OBSERVER = 'SpeckleConnector::Observers::ModelObserver'
end
@@ -7,6 +7,7 @@ require_relative 'platform_constants'
module SpeckleConnector
dir = __dir__.dup
dir.force_encoding('UTF-8') if dir.respond_to?(:force_encoding)
HOME_PATH = (ENV['HOME']).to_s
SPECKLE_SRC_PATH = Pathname.new(File.expand_path('..', dir)).cleanpath.to_s
SPECKLE_APPDATA_PATH = case OPERATING_SYSTEM
when OS_WIN
@@ -15,7 +15,16 @@ module SpeckleConnector
# @return [Integer] default chunk size the determine splitting base prop into chucks
attr_reader :default_chunk_size
def initialize(default_chunk_size = 1000)
# @return [String] stream id to send conversion
attr_reader :stream_id
attr_accessor :speckle_state
# @param stream_id [String] stream id to send conversion
def initialize(speckle_state, stream_id, preferences, default_chunk_size = 1000)
@speckle_state = speckle_state
@stream_id = stream_id
@preferences = preferences
@default_chunk_size = default_chunk_size
@detach_lineage = []
@lineage = []
@@ -30,31 +39,55 @@ module SpeckleConnector
def serialize(base)
id, traversed = traverse_base(base)
@objects[id] = traversed
return id, traversed
id
end
def total_children_count(id)
@objects[id][:totalChildrenCount]
end
# @param base [Object] base object to populate all children and their relationship
# @param base_and_entities [Object] base object to populate all children and their relationship
# rubocop:disable Metrics/MethodLength
def traverse_base(base)
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/AbcSize
def traverse_base(base_and_entities)
base, entities = base_and_entities
# 1. Create random string for lineage tracking.
@lineage.append(SecureRandom.hex)
# 2. Initialize traversed base object that will be filled with traversed values or
# 2. Get last item from detach_lineage array
is_detached = @detach_lineage.pop
# unless entities.nil?
# is_sent_before = entities.all? do |entity|
# check_base_available_on_state(entity, speckle_state)
# end
# if is_sent_before
# speckle_entity = speckle_state.speckle_entities[entities.first.persistent_id]
# ref_object = detach_helper(speckle_entity.id)
# parent = @lineage[-1]
# unless @family_tree[parent].nil?
# @family_tree[parent] = @family_tree[parent].merge(speckle_entity.speckle_object[:__closure])
# end
# @objects[speckle_entity.id] = ref_object if is_detached
# return speckle_entity.id, ref_object
# end
# end
# 3. Initialize traversed base object that will be filled with traversed values or
# traversed base objects as props.
traversed_base = SpeckleObjects::Base.new(speckle_type: base[:speckle_type], id: '')
# 3.1 Remove applicationId if it is nil
traversed_base.delete(:applicationId)
# 3. Iterate all entries (key, value) of the base {Base > Hash} object
# 4. Iterate all entries (key, value) of the base {Base > Hash} object
# speckle_state = traverse_base_props(base, traversed_base)
traverse_base_props(base, traversed_base)
# this is where all props are done for current `traversed_base`
# 4. Get last item from detach_lineage array
is_detached = @detach_lineage.pop
# 5. Add closures
closure = {}
parent = @lineage.pop
@@ -82,9 +115,23 @@ module SpeckleConnector
# 10. Save object string if detached
@objects[id] = traversed_base if is_detached
if @preferences[:user][:diffing] && !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_state = speckle_state.with_speckle_entity(speckle_entity)
end
end
return id, traversed_base
end
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
@@ -102,14 +149,16 @@ module SpeckleConnector
next
end
# 3.3. Determine prop is detached or not
is_prop_detach = prop[0] == '@'
# 3.3. Determine prop is dynamically detached or not
is_detach_prop = prop[0] == '@'
is_dynamically_detached = prop[0] == '@' && prop.length > 2 && prop[1] == '@'
prop = prop[2..-1] if is_dynamically_detached
# 3.4. Check prop needs to split into chunks
chunked_detach_match = prop.match(/^@\((\d*)\)/)
# 3.5. If split chunk is needed and prop value is array, then run chunking process
if value.is_a?(Array) && chunked_detach_match
if value.is_a?(Array) && !base_and_entities?(value) && chunked_detach_match
# 3.5.1. Determine chunk size, get it from prop if defined. ex: '@(31250)faces' -> 31250 = chunk size
chunk_size = chunked_detach_match[1] == '' ? default_chunk_size : chunked_detach_match[1].to_i
@@ -144,7 +193,7 @@ module SpeckleConnector
chunk_references = []
chunks.each do |chunk_element|
@detach_lineage.append(is_prop_detach)
@detach_lineage.append(is_detach_prop)
id, _traversed = traverse_base(chunk_element)
chunk_references.append(detach_helper(id))
end
@@ -152,17 +201,21 @@ module SpeckleConnector
# 3.5.7. Add chunk references to the traversed base prop without @(<chunk_size>)
traversed_base[prop.to_s.sub(chunked_detach_match[0], '')] = chunk_references
# 3.5.8. We are done chunking, good to go next
# 3.5.8. We are done chunking, good to go next property
next
end
child = traverse_value(value, is_detach_prop)
is_base = (value.is_a?(Hash) && !value[:speckle_type].nil?) ||
(base_and_entities?(value) && value[0].is_a?(Hash) && !value[0][:speckle_type].nil?)
# 3.6. traverse value according to value is a speckle object or not
if value.is_a?(Hash) && !value[:speckle_type].nil?
child = traverse_value(value, is_prop_detach)
traversed_base[prop] = is_prop_detach ? detach_helper(child[:id]) : child
else
traversed_base[prop] = traverse_value(value, is_prop_detach)
end
traversed_base[prop] = if is_base
is_detach_prop ? detach_helper(child[:id]) : child
else
child
end
end
end
# rubocop:enable Metrics/MethodLength
@@ -171,38 +224,57 @@ module SpeckleConnector
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# Whether value has a pattern [<converted>, [<entity>, <entity>, ... <entity>]] or not.
def base_and_entities?(value)
is_array = value.is_a?(Array)
return false unless is_array
return false unless is_array && value.length == 2
value[1].all? { |v| v.is_a?(Sketchup::Entity) }
end
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Style/OptionalBooleanParameter
# rubocop:disable Metrics/AbcSize
def traverse_value(value, is_detach = false)
# 1. Return same value if value is primitive type (string, numeric, boolean)
return value unless value.is_a?(Hash) || value.is_a?(Array)
# 2. Arrays
if value.is_a?(Array)
# 2. For pure arrays (Without referencing any Sketchup Entity)
if value.is_a?(Array) && !base_and_entities?(value)
# 2.1. If it is not detached then iterate array by traversing with their value
return value.collect { |el| traverse_value(el) } unless is_detach
unless is_detach
values = value.collect do |el|
el_value = traverse_value(el)
el_value
end
return values
end
# 2.2. If it is detached than collect them into detached_list
detached_list = []
value.each do |el|
if (el.is_a?(Array) || el.is_a?(Hash)) && !el[:speckle_type].nil?
if (el.is_a?(Hash) && !el[:speckle_type].nil?) || base_and_entities?(el)
@detach_lineage.append(is_detach)
id, _traversed_base = traverse_base(el)
detached_list.append(detach_helper(id))
else
detached_list.append(traverse_value(el, is_detach))
el_value = traverse_value(el, is_detach)
detached_list.append(el_value)
end
end
return detached_list
end
# 3. Hash
return value if value[:speckle_type].nil?
return value if value.is_a?(Hash) && value[:speckle_type].nil?
# 4. Base objects
unless value[:speckle_type].nil?
if (value.is_a?(Hash) && !value[:speckle_type].nil?) || base_and_entities?(value)
@detach_lineage.append(is_detach)
_id, traversed_base = traverse_base(value)
return traversed_base
@@ -215,6 +287,7 @@ module SpeckleConnector
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Style/OptionalBooleanParameter
# rubocop:enable Metrics/AbcSize
def detach_helper(reference_id)
@lineage.each do |parent|
@@ -263,6 +336,15 @@ module SpeckleConnector
batches
end
# rubocop:enable Metrics/MethodLength
# @param entity [Sketchup::Entity] source entity object
# @param speckle_state [States::SpeckleState] the current speckle state of the {States::State}
def check_base_available_on_state(entity, speckle_state)
is_exist = speckle_state.speckle_entities.keys.include?(entity.persistent_id)
return is_exist unless is_exist
speckle_state.speckle_entities[entity.persistent_id].valid_stream_ids.include?(stream_id)
end
end
end
end
@@ -7,15 +7,17 @@ module SpeckleConnector
# @return [Sketchup::Model] active sketchup model.
attr_reader :sketchup_model
attr_accessor :units, :definitions, :registry, :entity_observer
# @return [String] speckle units
attr_reader :units
def initialize(sketchup_model)
@sketchup_model = sketchup_model
su_unit = @sketchup_model.options['UnitsOptions']['LengthUnit']
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
@units = Converters::SKETCHUP_UNITS[su_unit]
@definitions = {}
# @registry = Sketchup.active_model.attribute_dictionary("speckle_id_registry", true)
# @entity_observer = SpeckleEntityObserver.new
end
end
end
@@ -35,6 +35,10 @@ module SpeckleConnector
Objects.Other.RenderMaterial
].freeze
def initialize(state)
super(state.sketchup_state)
end
def can_convert_to_native(obj)
return false unless obj.is_a?(Hash) && obj.key?('speckle_type')
@@ -74,6 +78,7 @@ module SpeckleConnector
# @param views [Array] views.
# @param sketchup_model [Sketchup::Model] active sketchup model.
# rubocop:disable Metrics/AbcSize
def create_views(views, sketchup_model)
return if views.empty?
@@ -86,6 +91,30 @@ module SpeckleConnector
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'])
page = sketchup_model.pages[view['name']]
set_page_update_properties(page, view['update_properties'])
set_rendering_options(page.rendering_options, view['rendering_options'])
end
end
# rubocop:enable Metrics/AbcSize
# @param page [Sketchup::Page] scene to update -update properties-
def 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 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
+122 -49
View File
@@ -2,13 +2,17 @@
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'
module SpeckleConnector
module Converters
@@ -17,101 +21,141 @@ module SpeckleConnector
# @return [Hash{Symbol=>Array}] layers to hold it's objects under the base object.
attr_reader :layers
def initialize(sketchup_model)
super(sketchup_model)
# @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)
state = speckle_state
sketchup_model.selection.each do |entity|
converted_object = convert(entity, preferences)
new_speckle_state, converted_object_with_entity = convert(entity, preferences, state)
state = new_speckle_state
layer_name = entity_layer_path(entity)
layers[layer_name].push(converted_object)
layers[layer_name].push(converted_object_with_entity)
end
# send only layers that have any object
# send only+ layers that have any object
base_object_properties = layers.reject { |_layer_name, objects| objects.empty? }
add_views(base_object_properties) if sketchup_model.pages.any?
SpeckleObjects::Base.with_detached_layers(base_object_properties)
return state, SpeckleObjects::Base.with_detached_layers(base_object_properties)
end
# Add views from pages.
# @param base_object_properties [Hash] dynamically attached base object properties.
# 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 [SpeckleObjects::Base] base object to serialize.
# @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 send_info(base)
serializer = SpeckleConnector::Converters::BaseObjectSerializer.new
# t = Time.now.to_f
id, _traversed = serializer.serialize(base)
# puts "Generating traversed object elapsed #{Time.now.to_f - t} s"
def serialize(base_and_entity, speckle_state, preferences, stream_id)
serializer = SpeckleConnector::Converters::BaseObjectSerializer.new(speckle_state, stream_id, preferences)
t = Time.now.to_f
id = serializer.serialize(base_and_entity)
batches = serializer.batch_objects
# write_to_speckle_folder(id, batches)
puts "Generating traversed object elapsed #{Time.now.to_f - t} s"
base_total_children_count = serializer.total_children_count(id)
return id, base_total_children_count, serializer.batch_objects
return id, base_total_children_count, batches, serializer.speckle_state
end
def write_to_speckle_folder(id, batches)
folder_path = "#{HOME_PATH}/Speckle"
file_path = "#{folder_path}/#{id}.json"
FileUtils.mkdir_p(folder_path) unless File.exist?(folder_path)
File.write(file_path, batches.first)
end
# @param entity [Sketchup::Entity] sketchup entity to convert Speckle.
def convert(entity, preferences)
# @param speckle_state [States::SpeckleState] the current speckle state of the {States::State}
# @param parent [Symbol, String] parent of the Sketchup Entity to be converted.
# rubocop:disable Metrics/MethodLength
def convert(entity, preferences, speckle_state, parent = :base)
convert = method(:convert)
if entity.is_a?(Sketchup::Edge)
return SpeckleObjects::Geometry::Line.from_edge(entity, @units, preferences[:model]).to_h
line = SpeckleObjects::Geometry::Line.from_edge(entity, @units, preferences[:model]).to_h
return speckle_state, [line, [entity]]
end
if entity.is_a?(Sketchup::Face)
return SpeckleObjects::Geometry::Mesh.from_face(entity, @units, preferences[:model])
mesh = SpeckleObjects::Geometry::Mesh.from_face(entity, @units, preferences[:model])
return speckle_state, [mesh, [entity]]
end
if entity.is_a?(Sketchup::Group)
return SpeckleObjects::Other::BlockInstance.from_group(entity, @units, @definitions, preferences, &convert)
new_speckle_state, block_instance = SpeckleObjects::Other::BlockInstance.from_group(
entity, @units, preferences, speckle_state, &convert
)
speckle_state = new_speckle_state
return speckle_state, [block_instance, [entity]]
end
if entity.is_a?(Sketchup::ComponentInstance)
return SpeckleObjects::Other::BlockInstance.from_component_instance(entity, @units, @definitions,
preferences, &convert)
end
if entity.is_a?(Sketchup::ComponentDefinition)
return SpeckleObjects::Other::BlockDefinition.from_definition(entity, @units, @definitions, preferences,
&convert)
new_speckle_state, block_instance = SpeckleObjects::Other::BlockInstance.from_component_instance(
entity, @units, preferences, speckle_state, &convert
)
speckle_state = new_speckle_state
return speckle_state, [block_instance, [entity]]
end
nil
if entity.is_a?(Sketchup::ComponentDefinition)
new_speckle_state, block_definition = SpeckleObjects::Other::BlockDefinition.from_definition(
entity, @units, @definitions, preferences, speckle_state, parent, &convert
)
speckle_state = new_speckle_state
return speckle_state, [block_definition, [entity]]
end
return speckle_state, nil
end
# rubocop:enable Metrics/MethodLength
# Create layers -> {Hash{Symbol=>Array}} from sketchup model with empty array as hash entry values.
# This method add first headless layers (not belong to any folder),
@@ -174,6 +218,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
@@ -0,0 +1,33 @@
# frozen_string_literal: true
require_relative 'event_observer'
module SpeckleConnector
module Observers
# App observer.
class AppObserver < Sketchup::AppObserver
include EventObserver
# rubocop:disable Naming/MethodName
# SketchUp observer method triggered when new empty model is created.
#
# @param model (Sketchup::Model): The active model object.
def onNewModel(model)
push_event(:onNewModel, model)
end
# SketchUp observer method triggered when previously saved model is opened.
#
# @param model (Sketchup::Model) - The active model object.
def onOpenModel(model)
push_event(:onOpenModel, model)
end
# SketchUp observer method triggered when user exists SketchUp.
def onQuit
push_event(:onQuit)
end
# rubocop:enable Naming/MethodName
end
end
end
@@ -1,26 +1,30 @@
# frozen_string_literal: true
require_relative 'event_observer'
module SpeckleConnector
module Observers
# Entities observer.
class EntitiesObserver < Sketchup::EntitiesObserver
attr_accessor :registry
def initialize
super()
@registry = Sketchup.active_model.attribute_dictionary('speckle_id_registry', true)
end
include EventObserver
# rubocop:disable Naming/MethodName
def onEraseEntity(entity)
app_id = entity.get_attribute('speckle', 'applicationId')
return if app_id.nil?
# @param entities (Sketchup::Entities)
# @param entity (Sketchup::Entity)
def onElementAdded(entities, entity)
push_event(:onElementAdded, entities, entity)
end
p(app_id)
# @param entities (Sketchup::Entities)
# @param entity (Sketchup::Entity)
def onElementModified(entities, entity)
push_event(:onElementModified, entities, entity)
end
@registry.delete_key(app_id)
p(@registry)
# @param entities (Sketchup::Entities)
# @param entity_id (Integer) id of the removed entity.
def onElementRemoved(entities, entity_id)
push_event(:onElementRemoved, entities, entity_id)
end
# rubocop:enable Naming/MethodName
end
@@ -0,0 +1,26 @@
# frozen_string_literal: true
require_relative 'event_hash'
require_relative '../actions/on_events_action'
module SpeckleConnector
module Observers
# Event handler class.
class EventHandler
# @return [SpeckleConnectorApp] an object that contains current state of speckle objects
attr_reader :app
def initialize(app)
@app = app
end
def handle_events(events)
run_handlers(events)
end
def run_handlers(events)
app.update_state!(Actions::OnEventsAction, events)
end
end
end
end
@@ -0,0 +1,20 @@
# frozen_string_literal: true
require_relative '../ext/immutable_ruby/core'
module SpeckleConnector
module Observers
# Collection of events.
class EventHash < Immutable::Hash
def push_event(observer_class, event_name, data)
observer_events = self[observer_class] || Immutable::EmptyHash
name_events = observer_events[event_name] || Immutable::EmptyVector
name_events = name_events.add(data)
observer_events = observer_events.put(event_name, name_events)
put(observer_class, observer_events)
end
end
EMPTY_EVENT_HASH = EventHash.new([])
end
end
@@ -0,0 +1,43 @@
# frozen_string_literal: true
require_relative 'event_hash'
require_relative '../actions/on_events_action'
module SpeckleConnector
module Observers
# Observer classes includes it to check common operations for all observer classes.
module EventObserver
# @return [ObserverHandler] handler for observer events
attr_reader :observer_handler
def initialize(observer_handler)
super()
@observer_handler = observer_handler
end
def push_event(event_name, *event_data)
return if observers_disabled?
@observer_handler.handle_event!(self.class.name, event_name, event_data)
end
def observers_disabled?
@observer_handler.observers_disabled?
end
# Push event only once. If the event is already registered, don't push it again
# @param event_name [Symbol] the name of the event
# @param event_data [Array] the optional data that comes with the event
def push_once(event_name, *event_data)
return if observers_disabled?
# Don't push anything if the selection event was already registered
class_events = @observer_handler.events[self.class]
events = class_events && class_events[event_name]
return if events&.any?
push_event(event_name, *event_data)
end
end
end
end
@@ -0,0 +1,30 @@
# frozen_string_literal: true
require_relative 'app_observer'
require_relative 'entities_observer'
require_relative 'observer_handler'
require_relative 'model_observer'
require_relative 'event_handler'
require_relative '../constants/observer_constants'
module SpeckleConnector
module Observers
# Factory class to create observers and it's handler
module Factory
module_function
def create_handler(app)
event_handler = EventHandler.new(app)
ObserverHandler.new(event_handler)
end
def create_observers(handler)
{
APP_OBSERVER => AppObserver.new(handler),
ENTITIES_OBSERVER => EntitiesObserver.new(handler),
MODEL_OBSERVER => ModelObserver.new(handler)
}.freeze
end
end
end
end
@@ -0,0 +1,19 @@
# frozen_string_literal: true
require_relative 'event_observer'
module SpeckleConnector
module Observers
# Model related event observers.
class ModelObserver < Sketchup::ModelObserver
include EventObserver
# rubocop:disable Naming/MethodName
# @param model (Sketchup::Model)
def onActivePathChanged(model)
push_event(:onActivePathChanged, model)
end
# rubocop:enable Naming/MethodName
end
end
end
@@ -0,0 +1,68 @@
# frozen_string_literal: true
require_relative 'event_hash'
module SpeckleConnector
module Observers
# Class to handle observer events.
class ObserverHandler
# @return [EventHash] registered events
attr_reader :events
# @return [Float] time when first observer was added
attr_reader :start_time
def initialize(event_handler)
@event_handler = event_handler
clear_events!
@observers_disabled = false
@observers_in_progress = false
@timer_in_progress = false
end
def handle_event!(observer_class, event_name, data)
puts "## Register #{observer_class} event #{event_name} ##"
puts " data = #{data.inspect}"
return if observers_disabled?
@events = @events.push_event(observer_class, event_name, data)
finish
end
def finish
return if @observers_in_progress
@observers_in_progress = true
@start_time = Time.now.to_f
UI.start_timer(0, false) { finish_in_timer }
end
def finish_in_timer
return if @timer_in_progress
@timer_in_progress = true
@event_handler.handle_events(events)
ensure
clear_events!
@observers_in_progress = false
@timer_in_progress = false
end
def observers_disabled?
@observers_disabled
end
def with_observers_disabled(&block)
previous_state = @observers_disabled
@observers_disabled = true
block.call
ensure
@observers_disabled = previous_state
end
def clear_events!
@events = EMPTY_EVENT_HASH
end
end
end
end
@@ -10,17 +10,26 @@ module SpeckleConnector
module Preferences
include Immutable::ImmutableUtils
DICT_HANDLER = SketchupModel::Dictionary::SpeckleModelDictionaryHandler
DEFAULT_PREFERENCES = "('configSketchup', '{\"DarkTheme\":false}');"
DEFAULT_CONFIG = "('configSketchup', '{\"dark_theme\":false, \"diffing\":false}');"
DEFAULT_PREFERENCES = '{"dark_theme":false, "diffing":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
db = Sqlite3::Database.new(SPECKLE_CONFIG_DB_PATH)
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
# Check configSketchup key is valid or not, otherwise init with default settings
if db.exec("SELECT content FROM 'objects' WHERE hash = 'configSketchup'").empty?
db.exec("INSERT INTO 'objects' VALUES #{DEFAULT_PREFERENCES}")
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'")
end
end
# Select data
@@ -30,7 +39,8 @@ module SpeckleConnector
data_hash = JSON.parse(data).to_h
# Get current theme value
dark_theme = data_hash['DarkTheme']
dark_theme = data_hash['dark_theme']
diffing = data_hash['diffing']
speckle_dictionary = sketchup_model.attribute_dictionary('Speckle')
@@ -38,7 +48,8 @@ module SpeckleConnector
Immutable::Hash.new(
{
user: {
dark_theme: dark_theme
dark_theme: dark_theme,
diffing: diffing
},
model: {
combine_faces_by_material: DICT_HANDLER.get_attribute(
@@ -84,7 +95,8 @@ module SpeckleConnector
Immutable::Hash.new(
{
user: {
dark_theme: dark_theme
dark_theme: dark_theme,
diffing: diffing
},
model: default_model_preferences
}
@@ -92,6 +104,7 @@ module SpeckleConnector
end
end
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/AbcSize
def self.default_model_preferences
{
@@ -8,6 +8,8 @@ module SpeckleConnector
# `A has child B` is Many to One relationship. Each person can have only one parent,
# but one parent can have several children.
class ManyToOneRelation
attr_reader :parent_table, :children_table
# @param child [Object] the child element in the relation to look for parent
# @return [Object] the parent element that is in relation with the child
# @return [nil] if the child element is not in the relation with any elements
@@ -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,7 +22,7 @@ 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.to_json unless IGNORED_DICTIONARY_NAMES.include?(att_dict.name)
end
dictionaries
end
@@ -25,6 +31,8 @@ module SpeckleConnector
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|
dict_name = dict_name == 'empty_dictionary_name' ? '' : dict_name
JSON.parse(entries).each do |key, value|
@@ -36,6 +44,18 @@ module SpeckleConnector
end
end
# 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?
JSON.parse(applied_schema_types).each do |key, value|
definition_entity.add_classification(key, value)
end
end
# @param entity [Sketchup::Entity] the sketchup entity of Speckle object
# @param key [Symbol] the name of the attribute
# @param dictionary_name [String, Symbol] the name of the attribute dictionary
@@ -11,16 +11,20 @@ module SpeckleConnector
class SpeckleEntityDictionaryHandler < DictionaryHandler
# Writes initial data while speckle entity is creating first time.
# @param sketchup_entity [Sketchup::Entity] Sketchup entity to write data into it's attribute dictionary.
def self.write_initial_base_data(sketchup_entity)
# rubocop:disable Metrics/ParameterLists
def self.write_initial_base_data(sketchup_entity, application_id, id, speckle_type, children_count, stream_id)
initial_dict_data = {
# Add here more if you want to write here initial data
SPECKLE_ID => '',
SPECKLE_TYPE => BASE_OBJECT,
APPLICATION_ID => '',
TOTAL_CHILDREN_COUNT => 0
SPECKLE_ID => id,
APPLICATION_ID => application_id,
SPECKLE_TYPE => speckle_type,
TOTAL_CHILDREN_COUNT => children_count,
VALID_STREAM_IDS => [stream_id],
INVALID_STREAM_IDS => []
}
set_hash(sketchup_entity, initial_dict_data)
end
# rubocop:enable Metrics/ParameterLists
end
end
end
@@ -0,0 +1,49 @@
# frozen_string_literal: true
module SpeckleConnector
# Operations related to {SketchupModel}.
module SketchupModel
# Materials to store for entities.
class Materials
def self.from_sketchup_model(model)
materials = model.materials
mat_hash = materials.collect { |material| [material.get_attribute(MAT_DICTIONARY, MAT_ID), material] }.to_h
Materials.new(mat_hash)
end
def initialize(material_hash = {})
@materials_by_id = material_hash.freeze
@id_by_materials = material_hash.collect { |id, material| [material, id] }.to_h.freeze
freeze
end
def add_material(id, material)
old_material = @materials_by_id[id.to_sym]
return self if material == old_material
new_material_hash = @materials_by_id.merge({ id.to_sym => material })
Materials.new(new_material_hash)
end
def by_id(id)
@materials_by_id[id.to_sym]
end
def material_id(material)
@id_by_materials[material]
end
def add_speckle_material
@materials_by_id[MAT_ADD]
end
def edit_speckle_material
@materials_by_id[MAT_EDIT]
end
def remove_speckle_material
@materials_by_id[MAT_REMOVE]
end
end
end
end
@@ -0,0 +1,30 @@
# frozen_string_literal: true
module SpeckleConnector
# Operations related to {SketchupModel}.
module SketchupModel
# Works directly with/on SketchUp Entities of different kinds (Groups, Faces, Edges, ...).
module Utils
# Static methods that work directly with Sketchup::Face objects
class FaceUtils
# A method that calculates the faces that are less than n-faces
# away from the current face. For n=1, you would get the
# neighbouring faces.
# @param edge_ary [Array<Sketchup::Edge>] the edges to look for n-adjacent faces
# @param n_adjacent [Integer] the distance from the edges (in number of faces)
# @return [Array<Sketchup::Face>] the faces that are n faces away from the given edges
def self.near_faces(edges_ary, n_adjacent = 1)
# get all the faces that are not more than a face away to the current face.
edges_ary = edges_ary.select(&:valid?)
adj_faces = []
n_adjacent.times do |_i|
new_faces = edges_ary.collect(&:faces).flatten.uniq
adj_faces += new_faces
edges_ary = new_faces.collect(&:edges).flatten.uniq - edges_ary
end
adj_faces.uniq
end
end
end
end
end
@@ -1,38 +0,0 @@
# frozen_string_literal: true
require_relative '../immutable/immutable'
require_relative '../convertors/units'
require_relative '../speckle_objects/base'
require_relative '../speckle_entities/speckle_line_entity'
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
module SpeckleConnector
module SpeckleEntities
# Speckle base entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
class SpeckleBaseEntity
include Immutable::ImmutableUtils
# @return [Sketchup::Entity] sketchup entity represents {SpeckleEntity} on the model.
attr_reader :sketchup_entity
# @return [SpeckleObjects::Base] speckle object that represented on server.
attr_reader :speckle_object
# @return [Integer] application id of the sketchup entity.
attr_reader :application_id
# @param sketchup_entity [Sketchup::Entity] sketchup entity represents {SpeckleEntity} on the model.
def initialize(sketchup_model, sketchup_entity)
@sketchup_entity = sketchup_entity
@application_id = @sketchup_entity.persistent_id
@speckle_object = SpeckleObjects::Base.new
su_unit = sketchup_model.options['UnitsOptions']['LengthUnit']
@units = Converters::SKETCHUP_UNITS[su_unit]
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.write_initial_base_data(@sketchup_entity)
end
def valid?
sketchup_entity.valid?
end
end
end
end
@@ -0,0 +1,23 @@
# 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
@@ -0,0 +1,27 @@
# 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
@@ -0,0 +1,36 @@
# 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
@@ -1,19 +1,126 @@
# frozen_string_literal: true
require_relative '../speckle_entities/speckle_line_entity'
require_relative 'speckle_entity_status'
require_relative '../immutable/immutable'
require_relative '../convertors/units'
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
module SpeckleConnector
module SpeckleEntities
# Speckle entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
module SpeckleEntity
def self.with_converted(skp_model, skp_entity)
# return the same object if it is already SpeckleEntity
return skp_entity if skp_entity.is_a?(SpeckleEntity)
return SpeckleBlockEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::Group)
return SpeckleBlockEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::ComponentInstance)
return SpeckleMeshEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::Face)
# Speckle base entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
class SpeckleEntity
include Immutable::ImmutableUtils
# @return [Sketchup::Entity] Sketchup Entity represents {SpeckleEntity} on the model.
attr_reader :sketchup_entity
SpeckleLineEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::Edge)
# @return [SpeckleObjects::Base] Speckle object that represented on server.
attr_reader :speckle_object
# @return [String] Speckle object type.
attr_reader :speckle_type
# @return [Integer] application id of the Sketchup Entity.
attr_reader :application_id
# @return [String] id of the Speckle Base Object
attr_reader :id
# @return [Integer] total children count of the Speckle Base Object
attr_reader :total_children_count
# @return [Hash{String=>SpeckleObjects::Base}] Speckle objects belongs to edge
attr_reader :speckle_children_objects
# @return [Array<String>] speckle entity that valid on streams
attr_reader :valid_stream_ids
# @return [Array<String>] speckle entity that invalid on streams
attr_reader :invalid_stream_ids
# @return [SpeckleEntityStatus] current status of the Speckle Entity.
attr_reader :status
attr_reader :source_material, :active_diffing_stream_id
# @param sketchup_entity [Sketchup::Entity] sketchup entity represents {SpeckleEntity} on the model.
def initialize(sketchup_entity, traversed_speckle_object, children, stream_id)
@status = SpeckleEntityStatus::UP_TO_DATE
@source_material = sketchup_entity.material
@active_diffing_stream_id = nil
@valid_stream_ids = [stream_id]
@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]
@speckle_children_objects = children
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
end
def with_up_to_date
with(:@status => SpeckleEntityStatus::UP_TO_DATE)
end
def with_invalid
valid = valid_stream_ids
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, VALID_STREAM_IDS, [])
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, INVALID_STREAM_IDS, valid)
with(:@valid_stream_ids => [], :@invalid_stream_ids => valid)
end
def activate_diffing(stream_id, material)
sketchup_entity.material = material
with(:@active_diffing_stream_id => stream_id)
end
def deactivate_diffing
sketchup_entity.material = @source_material
with(:@active_diffing_stream_id => nil)
end
def change_material(material)
sketchup_entity.material = material
end
def revert_material
sketchup_entity.material = source_material
end
def with_valid_stream_id(stream_id)
return self if valid_stream_ids.include?(stream_id)
invalid_ids = @invalid_stream_ids.include?(stream_id) ? @invalid_stream_ids - [stream_id] : @invalid_stream_ids
valid_ids = valid_stream_ids + [stream_id]
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, VALID_STREAM_IDS, valid_ids)
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, INVALID_STREAM_IDS, invalid_ids)
# if sketchup_entity.is_a?(Sketchup::Group) || sketchup_entity.is_a?(Sketchup::ComponentInstance)
# sketchup_entity.definition.set_attribute(SPECKLE_BASE_OBJECT, VALID_STREAM_IDS, valid_ids)
# sketchup_entity.definition.set_attribute(SPECKLE_BASE_OBJECT, INVALID_STREAM_IDS, invalid_ids)
# end
with(:@valid_stream_ids => @valid_stream_ids + [stream_id], :@invalid_stream_ids => invalid_ids)
end
def with_edited
with(:@status => SpeckleEntityStatus::EDITED)
end
def with_removed
with(:@status => SpeckleEntityStatus::REMOVED)
end
def valid?
sketchup_entity.valid?
end
end
end
@@ -0,0 +1,18 @@
# frozen_string_literal: true
require_relative '../immutable/immutable'
require_relative '../convertors/units'
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
module SpeckleConnector
module SpeckleEntities
module SpeckleEntityStatus
# Speckle Entity created first time with {Sketchup::Entity} before sent to server.
UP_TO_DATE = 0
# {Sketchup::Entity} that corresponds to Speckle Entity is edited after created by user.
EDITED = 1
# {Sketchup::Entity} that corresponds to Speckle Entity is removed.
REMOVED = 2
end
end
end
@@ -1,22 +1,20 @@
# frozen_string_literal: true
require_relative 'speckle_base_entity'
require_relative 'speckle_entity'
require_relative '../immutable/immutable'
require_relative '../speckle_objects/geometry/line'
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
module SpeckleConnector
module SpeckleEntities
# Speckle line entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
class SpeckleLineEntity < SpeckleBaseEntity
class SpeckleLineEntity < SpeckleEntities::SpeckleEntity
include Immutable::ImmutableUtils
# @return [SpeckleObjects::Geometry::Line] speckle line object
attr_reader :speckle_object
# @return [Hash{String=>SpeckleObjects::Base}] speckle objects belongs to edge
attr_reader :children
def initialize(sketchup_model, sketchup_edge)
super(sketchup_model, sketchup_edge)
@speckle_object = SpeckleObjects::Geometry::Line.from_edge(sketchup_edge, units)
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
@@ -0,0 +1,23 @@
# 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
@@ -20,9 +20,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,6 +39,8 @@ 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
end
@@ -25,16 +25,15 @@ module SpeckleConnector
# @param units [String] units of the speckle mesh.
# @param render_material [Other::RenderMaterial, nil] render material of the speckle mesh.
# @param bbox [Geometry::BoundingBox] bounding box speckle object of the speckle mesh.
# @param vertices [Array] vertices of the speckle mesh.
# @param faces [Array] faces of the speckle mesh.
# @param sketchup_attributes [Hash] additional information about speckle mesh.
# rubocop:disable Metrics/ParameterLists
def initialize(units:, render_material:, bbox:, vertices:, faces:, sketchup_attributes:)
def initialize(units:, render_material:, vertices:, faces:, sketchup_attributes:, application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: nil,
application_id: application_id,
id: nil
)
@vertices = []
@@ -42,7 +41,7 @@ module SpeckleConnector
@units = units
self[:units] = units
self[:renderMaterial] = render_material
self[:bbox] = bbox
# self[:bbox] = bbox
self[:'@(31250)vertices'] = vertices
self[:'@(62500)faces'] = faces
self[:sketchup_attributes] = sketchup_attributes if sketchup_attributes.any?
@@ -102,11 +101,11 @@ module SpeckleConnector
units: units,
render_material: face.material.nil? && face.back_material.nil? ? nil : Other::RenderMaterial
.from_material(face.material || face.back_material),
bbox: Geometry::BoundingBox.from_bounds(face.bounds, units),
vertices: [], # mesh.nil? ? face_vertices_to_array(face, units) : mesh_points_to_array(mesh, units),
faces: [], # mesh.nil? ? face_indices_to_array(face, 0) : mesh_faces_to_array(mesh, -1),
# face_edge_flags: [], # mesh.nil? ? face_edge_flags_to_array(face) : mesh_edge_flags_to_array(mesh),
sketchup_attributes: att
# bbox: Geometry::BoundingBox.from_bounds(face.bounds, units),
vertices: [],
faces: [],
sketchup_attributes: att,
application_id: face.persistent_id
)
speckle_mesh.face_to_mesh(face)
speckle_mesh.update_mesh
@@ -2,7 +2,6 @@
require_relative 'length'
require_relative '../base'
require_relative '../../typescript/typescript_object'
module SpeckleConnector
module SpeckleObjects
@@ -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,11 +31,10 @@ 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.
self['@geometry'] = geometry
self['@@geometry'] = geometry
end
# rubocop:enable Metrics/ParameterLists
@@ -48,7 +46,8 @@ module SpeckleConnector
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
def self.from_definition(definition, units, definitions, preferences, &convert)
# rubocop:disable Metrics/ParameterLists
def self.from_definition(definition, units, definitions, preferences, speckle_state, parent, &convert)
guid = definition.guid
return definitions[guid] if definitions.key?(guid)
@@ -67,28 +66,39 @@ module SpeckleConnector
# TODO: Solve logic
geometry = if definition.entities[0].is_a?(Sketchup::Edge) || definition.entities[0].is_a?(Sketchup::Face)
group_entities_to_speckle(definition, units, definitions, preferences, &convert)
new_speckle_state, geo = group_entities_to_speckle(
definition, preferences, speckle_state, parent, &convert
)
speckle_state = new_speckle_state
geo
else
definition.entities.map do |entity|
convert.call(entity, preferences) unless entity.is_a?(Sketchup::Edge) && entity.faces.any?
next if entity.is_a?(Sketchup::Edge) && entity.faces.any?
new_speckle_state, converted = convert.call(entity, preferences,
speckle_state,
definition.persistent_id)
speckle_state = new_speckle_state
converted
end
end
# FIXME: Decide how to approach base point of the definition instead origin.
BlockDefinition.new(
block_definition = BlockDefinition.new(
units: units,
name: definition.name,
base_point: Geometry::Point.new(0, 0, 0, units),
geometry: geometry,
always_face_camera: definition.behavior.always_face_camera?,
sketchup_attributes: att,
application_id: guid
application_id: definition.persistent_id.to_s
)
return speckle_state, block_definition
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/ParameterLists
# Finds or creates a component definition from the geometry and the given name
# @param sketchup_model [Sketchup::Model] sketchup model to check block definitions.
@@ -126,35 +136,47 @@ module SpeckleConnector
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def self.group_entities_to_speckle(definition, units, definitions, preferences, &convert)
def self.group_entities_to_speckle(definition, preferences, speckle_state, parent, &convert)
orphan_edges = definition.entities.grep(Sketchup::Edge).filter { |edge| edge.faces.none? }
lines = orphan_edges.collect do |orphan_edge|
Geometry::Line.from_edge(orphan_edge, units, preferences[:model])
new_speckle_state, converted = convert.call(orphan_edge, preferences, speckle_state, parent)
speckle_state = new_speckle_state
converted
end
nested_blocks = definition.entities.grep(Sketchup::ComponentInstance).collect do |component_instance|
BlockInstance.from_component_instance(component_instance, units, definitions, preferences, &convert)
new_speckle_state, converted = convert.call(component_instance, preferences, speckle_state, parent)
speckle_state = new_speckle_state
converted
end
nested_groups = definition.entities.grep(Sketchup::Group).collect do |group|
BlockInstance.from_group(group, units, definitions, preferences, &convert)
new_speckle_state, converted = convert.call(group, preferences, speckle_state, parent)
speckle_state = new_speckle_state
converted
end
if preferences[:model][:combine_faces_by_material]
mesh_groups = {}
definition.entities.grep(Sketchup::Face).collect do |face|
group_meshes_by_material(face, mesh_groups, units, preferences[:model])
new_speckle_state = group_meshes_by_material(
face, mesh_groups, speckle_state, preferences, parent, &convert
)
speckle_state = new_speckle_state
end
# Update mesh overwrites points and polygons into base object.
mesh_groups.each { |_, mesh| mesh.update_mesh }
mesh_groups.each { |_, mesh| mesh.first.update_mesh }
lines + nested_blocks + nested_groups + mesh_groups.values
return speckle_state, lines + nested_blocks + nested_groups + mesh_groups.values
else
meshes = definition.entities.grep(Sketchup::Face).collect do |face|
Geometry::Mesh.from_face(face, units, preferences[:model])
meshes = []
definition.entities.grep(Sketchup::Face).collect do |face|
new_speckle_state, converted = convert.call(face, preferences, speckle_state, parent)
meshes.append(converted)
speckle_state = new_speckle_state
end
lines + nested_blocks + nested_groups + meshes
return speckle_state, lines + nested_blocks + nested_groups + meshes
end
end
# rubocop:enable Metrics/AbcSize
@@ -162,20 +184,26 @@ module SpeckleConnector
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
def self.group_meshes_by_material(face, mat_groups, units, model_preferences)
# rubocop:disable Metrics/ParameterLists
def self.group_meshes_by_material(face, mesh_groups, speckle_state, preferences, parent, &convert)
# convert material
mat_id = get_mesh_group_id(face, model_preferences)
mat_groups[mat_id] = Geometry::Mesh.from_face(face, units, model_preferences) unless mat_groups.key?(mat_id)
mat_group = mat_groups[mat_id]
mat_group.face_to_mesh(face)
mesh_group_id = get_mesh_group_id(face, preferences[:model])
new_speckle_state, converted = convert.call(face, preferences, speckle_state, parent)
mesh_groups[mesh_group_id] = converted unless mesh_groups.key?(mesh_group_id)
mesh_group = mesh_groups[mesh_group_id]
mesh_group[0].face_to_mesh(face)
mesh_group[1].append(face)
new_speckle_state
end
# rubocop:enable Metrics/ParameterLists
# Mesh group id helps to determine how to group faces into meshes.
# @param face [Sketchup::Face] face to get mesh group id.
def self.get_mesh_group_id(face, model_preferences)
if model_preferences[:include_entity_attributes] && model_preferences[:include_face_entity_attributes]
has_attribute_dictionary = !(face.attribute_dictionaries.nil? || face.attribute_dictionaries.first.nil?)
return face.persistent_id.to_s if has_attribute_dictionary
if model_preferences[:include_entity_attributes] &&
model_preferences[:include_face_entity_attributes] &&
attribute_dictionary?(face)
return face.persistent_id.to_s
end
material = face.material || face.back_material
@@ -183,6 +211,23 @@ module SpeckleConnector
return material.entityID.to_s
end
def self.attribute_dictionary?(face)
any_attribute_dictionary = !(face.attribute_dictionaries.nil? || face.attribute_dictionaries.first.nil?)
return any_attribute_dictionary unless any_attribute_dictionary
# If there are any attribute dictionary, then make sure that they are not ignored ones.
all_attribute_dictionary_ignored = face.attribute_dictionaries.all? do |dict|
ignored_dictionaries.include?(dict.name)
end
!all_attribute_dictionary_ignored
end
def self.ignored_dictionaries
[
'Speckle_Base_Object'
]
end
end
end
end
@@ -38,33 +38,45 @@ 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
# @param group [Sketchup::Group] group to convert Speckle BlockInstance
def self.from_group(group, units, component_defs, preferences, &convert)
def self.from_group(group, units, preferences, speckle_state, &convert)
new_speckle_state, block_definition = convert.call(group.definition, preferences, speckle_state,
group.persistent_id)
speckle_state = new_speckle_state
dictionaries = {}
if preferences[:model][:include_entity_attributes] && preferences[:model][:include_group_entity_attributes]
dictionaries = SketchupModel::Dictionary::DictionaryHandler.attribute_dictionaries_to_speckle(group)
end
att = dictionaries.any? ? { dictionaries: dictionaries } : {}
BlockInstance.new(
block_instance = BlockInstance.new(
units: units,
is_sketchup_group: true,
name: group.name == '' ? nil : group.name,
render_material: group.material.nil? ? nil : RenderMaterial.from_material(group.material),
transform: Other::Transform.from_transformation(group.transformation, units),
block_definition: BlockDefinition.from_definition(group.definition, units, component_defs,
preferences, &convert),
block_definition: block_definition,
sketchup_attributes: att,
application_id: group.guid
)
return speckle_state, block_instance
end
# @param component_instance [Sketchup::ComponentInstance] component instance to convert Speckle BlockInstance
# rubocop:disable Metrics/MethodLength
def self.from_component_instance(component_instance, units, component_defs, preferences, &convert)
def self.from_component_instance(component_instance, units, preferences, speckle_state, &convert)
new_speckle_state, block_definition = convert.call(
component_instance.definition,
preferences,
speckle_state,
component_instance.persistent_id
)
speckle_state = new_speckle_state
dictionaries = {}
if preferences[:model][:include_entity_attributes] &&
preferences[:model][:include_component_entity_attributes]
@@ -72,7 +84,8 @@ module SpeckleConnector
.attribute_dictionaries_to_speckle(component_instance)
end
att = dictionaries.any? ? { dictionaries: dictionaries } : {}
BlockInstance.new(
block_instance = BlockInstance.new(
units: units,
is_sketchup_group: false,
name: component_instance.name == '' ? nil : component_instance.name,
@@ -82,11 +95,11 @@ module SpeckleConnector
RenderMaterial.from_material(component_instance.material)
end,
transform: Other::Transform.from_transformation(component_instance.transformation, units),
block_definition: BlockDefinition.from_definition(component_instance.definition, units, component_defs,
preferences, &convert),
block_definition: block_definition,
sketchup_attributes: att,
application_id: component_instance.guid
application_id: component_instance.persistent_id.to_s
)
return speckle_state, block_instance
end
# rubocop:enable Metrics/MethodLength
@@ -106,8 +119,9 @@ module SpeckleConnector
# 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']
# NOTE: nil checks for backward compatibility
block_definition = block['definition'] || block['blockDefinition'] || block['@blockDefinition']
geometry = block_definition['geometry'] || block_definition['@geometry']
definition = BlockDefinition.to_native(
sketchup_model,
geometry,
@@ -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
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require_relative '../base'
require_relative '../../typescript/typescript_object'
module SpeckleConnector
module SpeckleObjects
@@ -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
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative '../immutable/immutable'
require_relative '../sketchup_model/materials/materials'
module SpeckleConnector
module States
@@ -10,9 +11,13 @@ module SpeckleConnector
# @return [Sketchup::Model] active model on the sketchup
attr_reader :sketchup_model
# @return [SketchupModel::Materials] materials by their id
attr_reader :materials
# @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)
end
# @return [Integer] length units code of the sketchup model.
+41 -1
View File
@@ -2,6 +2,7 @@
require_relative '../immutable/immutable'
require_relative '../callbacks/callback_message'
require_relative '../speckle_entities/speckle_entity'
module SpeckleConnector
module States
@@ -9,19 +10,33 @@ module SpeckleConnector
class SpeckleState
include Immutable::ImmutableUtils
# @return [ImmutableHash{Integer=>SpeckleEntities::SpeckleEntity}] persistent_id of the sketchup entity and
# corresponding speckle entity
attr_reader :speckle_entities
# @return [Array] accounts on appdata.
attr_reader :accounts
# @return [Hash{Class => Observer}] the observer objects that are used to attach to objects in Sketchup to collect
# events that are triggered from Sketchup
attr_reader :observers
# @return [Hash] queue to send to server.
attr_reader :message_queue
# @return [Hash] stream queue to send to server.
attr_reader :stream_queue
def initialize(accounts, queue, stream_queue)
# @return [Relations::ManyToOneRelation] relations between objects.
attr_accessor :relation
def initialize(accounts, observers, queue, stream_queue)
@accounts = accounts
@observers = observers
@message_queue = queue
@stream_queue = stream_queue
@speckle_entities = Immutable::EmptyHash
@relation = Relations::ManyToOneRelation.new
end
# @param callback_name [String] name of the callback command
@@ -33,9 +48,34 @@ module SpeckleConnector
with(:@message_queue => new_queue)
end
def with_invalid_streams_queue
new_queue = message_queue.merge({ "updateInvalidStreams":
"updateInvalidStreams(#{JSON.generate(invalid_streams)})" })
with(:@message_queue => new_queue)
end
def with_accounts(new_accounts)
with(:@accounts => new_accounts)
end
def with_speckle_entity(traversed_entity)
new_speckle_entities = speckle_entities.put(traversed_entity.application_id, traversed_entity)
with_speckle_entities(new_speckle_entities)
end
def with_speckle_entities(new_speckle_entities)
with(:@speckle_entities => new_speckle_entities)
end
def with_relation(new_relation)
with(:@relation => new_relation)
end
def invalid_streams
speckle_entities.collect do |_id, speckle_entity|
speckle_entity.invalid_stream_ids
end.reduce([], :concat).uniq
end
end
end
end
@@ -1,57 +0,0 @@
# frozen_string_literal: true
require 'json'
module SpeckleConnector
module Typescript
# Object to help convert object attributes to JSON and by checking types.
class TypescriptObject
# @param attributes [Hash{Symbol=>Object}] attributes are given as key value pairs
def initialize(**attributes)
@attributes = attributes
check_attributes
end
# @return [String] the JSON representation of the object
def to_json(*options)
@attributes.to_json(*options)
end
def to_h(*options)
JSON.parse(to_json(*options), { symbolize_names: true })
end
private
# rubocop:disable Metrics/CyclomaticComplexity
def check_attributes
attribute_types.each do |key, class_or_classes|
value = @attributes[key]
case class_or_classes
when Array
is_class_correct = class_or_classes.any? do |klass|
raise "#{klass} is not a class" unless klass.is_a? Class
value.is_a? klass
end
raise "attribute #{key} is of class #{value.class} and not #{class_or_classes}" unless is_class_correct
when Class
raise ArgumentError, "#{class_or_classes} should be class" unless class_or_classes.is_a? Class
unless value.is_a? class_or_classes
raise ArgumentError,
"attribute #{key} is of class #{value.class} and not #{class_or_classes}"
end
else
raise ArgumentError, "#{class_or_classes} should be class or array of classes"
end
end
end
# rubocop:enable Metrics/CyclomaticComplexity
def attribute_types
raise NotImplementedError, 'Implement in child class'
end
end
end
end
+1 -1
View File
@@ -97,7 +97,7 @@ module SpeckleConnector
commands = CommandParser.parse_commands(data)
commands.each do |cmd|
puts '### COMMAND CALLED BY DIALOG ###'
puts "name: #{cmd.name}, data: #{cmd.data}"
puts "name: #{cmd.name}"
@ready = true if cmd.name == DIALOG_READY
@commands[cmd.name].run(cmd.data)
end
+5 -1
View File
@@ -13,11 +13,13 @@ require_relative '../commands/remove_stream'
require_relative '../commands/notify_connected'
require_relative '../commands/user_preferences_updated'
require_relative '../commands/model_preferences_updated'
require_relative '../commands/activate_diffing'
require_relative '../actions/reload_accounts'
require_relative '../actions/load_saved_streams'
require_relative '../actions/init_local_accounts'
require_relative '../actions/collect_preferences'
require_relative '../actions/deactivate_diffing'
require_relative '../actions/collect_versions'
module SpeckleConnector
@@ -68,7 +70,9 @@ module SpeckleConnector
collect_preferences: Commands::ActionCommand.new(@app, Actions::CollectPreferences),
collect_versions: Commands::ActionCommand.new(@app, Actions::CollectVersions),
user_preferences_updated: Commands::UserPreferencesUpdated.new(@app),
model_preferences_updated: Commands::ModelPreferencesUpdated.new(@app)
model_preferences_updated: Commands::ModelPreferencesUpdated.new(@app),
activate_diffing: Commands::ActivateDiffing.new(@app),
deactivate_diffing: Commands::ActionCommand.new(@app, Actions::DeactivateDiffing)
}.freeze
end
end
@@ -0,0 +1,32 @@
# frozen_string_literal: true
require_relative '../../test_helper'
require_relative '../../../speckle_connector/src/states/speckle_state'
require_relative '../../../speckle_connector/src/speckle_objects/geometry/point'
require_relative '../../../speckle_connector/src/speckle_objects/geometry/line'
require_relative '../../../speckle_connector/src/convertors/base_object_serializer'
module SpeckleConnector
module Converters
class BaseObjectSerializerTest < Minitest::Test
def setup
# Do nothing
end
def teardown
# Do nothing
end
def test_base_initialize
speckle_state = States::SpeckleState.new({}, {}, {}, {})
start_point = SpeckleObjects::Geometry::Point.new(0.0, 0.0, 0.0, 'm')
end_point = SpeckleObjects::Geometry::Point.new(10.0, 10.0, 0.0, 'm')
line = SpeckleObjects::Geometry::Line.test_line(start_point, end_point, 'm')
serializer = Converters::BaseObjectSerializer.new
new_speckle_state, id, traversed = serializer.serialize(line, speckle_state)
assert_equal(id, "a53c02860be04238514f1a5b00883fe2")
end
end
end
end
+52 -13
View File
@@ -5,7 +5,11 @@
@mouseenter="hover = true"
@mouseleave="hover = false"
>
<v-toolbar flat height="70">
<v-toolbar flat height="70" :color="getColor(invalid)">
<v-btn v-tooptip="''" icon small outlined class="delta-btn" v-if="invalid" @click="activateDiffing">
<v-icon v-if="!diffing" class="toggleUpDown" :class='{ "rotate": diffing }' small>mdi-triangle-outline</v-icon>
<v-icon v-else class="toggleUpDown" :class='{ "rotate": diffing }' small>mdi-triangle</v-icon>
</v-btn>
<v-toolbar-title class="ml-0" style="position: relative; left: -10px">
<!-- Uncomment when pinning is in place and add style="position: relative; left: -10px" to the element above :) -->
<v-btn
@@ -157,7 +161,6 @@ import gql from 'graphql-tag'
import { bus } from '../main'
import streamQuery from '../graphql/stream.gql'
import ObjectLoader from '@speckle/objectloader'
import { BaseObjectSerializer } from '../utils/serialization'
global.convertedFromSketchup = function (streamId, batches, commitId, totalChildrenCount) {
bus.$emit(`sketchup-objects-${streamId}`, batches, commitId, totalChildrenCount)
@@ -198,7 +201,9 @@ export default {
loadingStage: null,
branchName: 'main',
commitId: 'latest',
commitMessage: null
commitMessage: null,
invalid: false,
diffing: false
}
},
apollo: {
@@ -280,6 +285,17 @@ export default {
}
},
mounted() {
bus.$on(`deactivate-diffing-${this.streamId}`, () => {
this.diffing = false
})
bus.$on(`validate-stream-${this.streamId}`, () => {
this.invalid = false
console.log(`validate-stream-${this.streamId}`)
})
bus.$on(`invalidate-stream-${this.streamId}`, () => {
this.invalid = true
console.log(`invalidate-stream-${this.streamId}`)
})
bus.$on(`refresh-stream-${this.streamId}`, () => {
let oldBranchName = this.branchName
let oldCommitId = this.commitId
@@ -315,6 +331,13 @@ export default {
if (this.saved) sketchup.exec({name: "notify_connected", data: {stream_id: this.streamId}})
},
methods: {
getColor(invalid){
if(invalid){
return "#ffdfdf"
} else {
return ""
}
},
sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
},
@@ -331,6 +354,16 @@ export default {
this.$mixpanel.track('Connector Action', { name: 'Commit Switch' })
this.commitId = commitId
},
activateDiffing(){
if (this.diffing){
this.diffing = false
sketchup.exec({name: "deactivate_diffing", data: {}})
return
}
this.diffing = true
bus.$emit("deactivate-diffing-except", (this.streamId))
sketchup.exec({name: "activate_diffing", data: {stream_id: this.streamId}})
},
toggleSavedStream() {
if (this.saved) {
sketchup.exec({name: "remove_stream", data: {stream_id: this.streamId}})
@@ -406,16 +439,6 @@ export default {
return
}
// let s = new BaseObjectSerializer()
// let startTime = new Date()
// let { hash, serialized } = s.writeJson(objects)
// let endTime = new Date();
// let timeDiff = endTime - startTime; //in ms
// // strip the ms
// timeDiff /= 1000;
// // get seconds
// console.log(timeDiff + " seconds");
try {
this.loadingStage = 'uploading'
this.loadingSend = true
@@ -488,6 +511,22 @@ export default {
</script>
<style scoped>
.delta-btn.v-btn--outlined {
border: 1px solid #474747;
background: white;
margin-left: -90px;
margin-top: -50px;
position: absolute;
}
.toggleUpDown {
transition: transform .3s ease-in-out !important;
}
.toggleUpDown.rotate {
transform: rotate(180deg);
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.2s ease-in;
+20 -1
View File
@@ -23,6 +23,14 @@
<v-icon>mdi-theme-light-dark</v-icon>
</v-btn>
<span>Color Mode</span>
<!-- Switch Diffing -->
<v-switch
v-model="diffing"
class="pt-3 mt-n2 mb-n2"
:label="'Diffing (Alpha)'"
@click="switchDiffing"
/>
<div class="sm1 mt-3">Send Strategy</div>
<v-divider class="mb-2"/>
<v-switch
@@ -112,6 +120,7 @@ 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
}
},
watch: {
@@ -192,6 +201,16 @@ export default {
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: {
@@ -199,7 +218,7 @@ export default {
this.$vuetify.theme.dark = !this.$vuetify.theme.dark
sketchup.exec({
name: "user_preferences_updated",
data: {preference_hash: "configSketchup", preference: "DarkTheme", value: this.$vuetify.theme.dark}
data: {preference_hash: "configSketchup", preference: "dark_theme", value: this.$vuetify.theme.dark}
})
this.$mixpanel.track('Connector Action', { name: 'Toggle Theme' })
},
+28
View File
@@ -44,6 +44,14 @@ global.setSavedStreams = function (streamIds) {
bus.$emit('set-saved-streams', streamIds)
}
global.updateInvalidStreams = function (streamIds) {
bus.$emit('validate-streams')
streamIds.forEach((id) => {
bus.$emit(`invalidate-stream-${id}`)
})
}
const streamLimit = 5
export default {
name: 'Streams',
@@ -72,6 +80,26 @@ export default {
}
},
mounted() {
bus.$on("deactivate-diffing-except", (exceptedStreamId) => {
this.savedStreams.forEach((streamId) => {
if (streamId !== exceptedStreamId){
bus.$emit(`deactivate-diffing-${streamId}`)
}
})
this.allStreamsList.forEach((stream) => {
if (stream.id !== exceptedStreamId){
bus.$emit(`deactivate-diffing-${stream.id}`)
}
})
})
bus.$on('validate-streams', () => {
this.savedStreams.forEach((streamId) => {
bus.$emit(`validate-stream-${streamId}`)
})
this.allStreamsList.forEach((stream) => {
bus.$emit(`validate-stream-${stream.id}`)
})
})
bus.$on('refresh-streams', () => {
// TODO: We should remember selected branches and commits before refetch
this.$apollo.queries.streams.refetch()