Compare commits

...

7 Commits

Author SHA1 Message Date
Oğuzhan Koral 914f6e9516 Fix (Model): Update preferences on state and UI when model has changed 2023-02-21 13:01:53 +03:00
oguzhankoral fa6f65cb7c Use handler methods instead watchers per switch 2023-02-21 12:57:24 +03:00
oguzhankoral dafbe6f995 Update preferences on state and UI when model has changed 2023-02-21 10:47:49 +03:00
Oğuzhan Koral 4a8ac6377b Fix (Preferences): Validate preferences if data is incomplete 2023-02-20 16:47:55 +03:00
oguzhankoral c91fac4ec4 Validate preferences if data is incomplete 2023-02-20 16:30:46 +03:00
Oğuzhan Koral 00d23b084e Chore (Attributes): Send receive attributes as deserialized 2023-02-20 14:58:21 +03:00
oguzhankoral e0a3b82cea Send receive attributes as deserialized 2023-02-20 14:56:01 +03:00
7 changed files with 162 additions and 122 deletions
@@ -2,6 +2,7 @@
require_relative 'event_action'
require_relative '../load_sketchup_model'
require_relative '../collect_preferences'
module SpeckleConnector
module Actions
@@ -18,7 +19,13 @@ module SpeckleConnector
return state unless event_data&.any?
model = event_data.flatten.first
Actions::LoadSketchupModel.update_state(state, model)
# LoadSketchupModel action should be responsible to update all model specific data for state and then
# should notify the UI to update it's components.
new_state = Actions::LoadSketchupModel.update_state(state, model)
# Action to let UI to render itself with new preferences state
# TODO: Later UI should be updated if any stream is invalid after
# we collected speckle_entities appropriately
CollectPreferences.update_state(new_state, {})
end
end
@@ -12,8 +12,8 @@ module SpeckleConnector
# Handle the events that were collected by the observer. In case of the selection observer,
# we only need to handle the events once if any of the events actually happened.
# @param event_data [Hash{Symbol=>Array}] the event data grouped by the event name
# @param state [States::State] the current state of the SpeckleConnector Application
# @param events [Hash{Symbol=>Array}] the event data grouped by the event name
# @return [States::State] the transformed state
def self.update_state(state, events)
# Don't do anything if there are no events for this action
@@ -20,7 +20,7 @@ module SpeckleConnector
speckle_state = States::SpeckleState.new(accounts, observers, {}, {})
# This should be the only point that `Sketchup_active_model` passed to application state.
sketchup_state = States::SketchupState.new(Sketchup.active_model)
preferences = Preferences.init_preferences(sketchup_state.sketchup_model)
preferences = Preferences.read_preferences(sketchup_state.sketchup_model)
user_state_with_preferences = state.user_state.with_preferences(preferences)
state = States::State.new(user_state_with_preferences, speckle_state, sketchup_state, false)
Actions::LoadSketchupModel.update_state(state, sketchup_state.sketchup_model)
@@ -2,7 +2,9 @@
require_relative 'action'
require_relative 'initialize_materials'
require_relative '../preferences/preferences'
require_relative '../states/state'
require_relative '../states/sketchup_state'
require_relative '../constants/observer_constants'
module SpeckleConnector
@@ -15,11 +17,18 @@ module SpeckleConnector
# @param additional_parameters [Array] parameters that the action takes
# @return [States::State] the new updated state object
def self.update_state(state, sketchup_model)
# new_model_state = SketchupModel::Readers::ModelReader.read_model(sketchup_model)
# new_model_state = InitializeMaterials.update_state(new_model_state)
new_sketchup_state = state.sketchup_state.with(:@sketchup_model => sketchup_model)
# Init sketchup state again with new model
new_sketchup_state = States::SketchupState.new(sketchup_model)
new_state = state.with(:@sketchup_state => new_sketchup_state)
# Init materials again
new_state = InitializeMaterials.update_state(new_state)
# TODO: Read here SpeckleEntities if they exist in model.
# Read preferences from database and model.
preferences = Preferences.read_preferences(new_state.sketchup_state.sketchup_model)
new_user_state = new_state.user_state.with_preferences(preferences)
new_state = new_state.with(:@user_state => new_user_state)
attach_observers(sketchup_model, new_state.speckle_state.observers)
new_state
end
@@ -7,6 +7,7 @@ require_relative '../sketchup_model/dictionary/speckle_model_dictionary_handler'
module SpeckleConnector
# Preferences that stored on config database and sketchup_model.
# rubocop:disable Metrics/ModuleLength
module Preferences
include Immutable::ImmutableUtils
DICT_HANDLER = SketchupModel::Dictionary::SpeckleModelDictionaryHandler
@@ -15,33 +16,17 @@ module SpeckleConnector
# @param sketchup_model [Sketchup::Model] active model.
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
def self.init_preferences(sketchup_model)
# Init sqlite database
def self.read_preferences(sketchup_model)
db = Sqlite3::Database.new(SPECKLE_CONFIG_DB_PATH)
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 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
validate_preferences(db)
# Select data
data = db.exec("SELECT content FROM 'objects' WHERE hash = 'configSketchup'").first.first
row_data = db.exec("SELECT content FROM 'objects' WHERE hash = 'configSketchup'").first.first
# Parse string to hash
data_hash = JSON.parse(data).to_h
data_hash = JSON.parse(row_data).to_h
# Get current theme value
dark_theme = data_hash['dark_theme']
diffing = data_hash['diffing']
speckle_dictionary = sketchup_model.attribute_dictionary('Speckle')
if speckle_dictionary
@@ -104,7 +89,6 @@ module SpeckleConnector
end
end
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/AbcSize
def self.default_model_preferences
{
@@ -117,5 +101,38 @@ module SpeckleConnector
merge_coplanar_faces: true
}
end
# Whether row data is complete with preference or not.
# It is useful for backward compatibility, when we add new preferences it should be reset when user initialize it.
def self.data_complete?(row_data)
return false if row_data.empty?
data = JSON.parse(row_data.first.first)
return false if data['dark_theme'].nil? || data['diffing'].nil?
true
end
# Validates current preferences. If there are incomplete data then this method resets it with default preferences.
# @param database [Sqlite3::Database] database for queries.
def self.validate_preferences(database)
row_data = database.exec("SELECT content FROM 'objects' WHERE hash = 'configSketchup'")
is_config_sketchup_exist = !row_data.empty?
is_data_complete = data_complete?(row_data)
# rubocop:disable Style/GuardClause
if !is_config_sketchup_exist || !is_data_complete
if is_config_sketchup_exist
unless is_data_complete
# Update table with default preferences
database.exec("UPDATE 'objects' SET content = '#{DEFAULT_PREFERENCES}' WHERE hash = 'configSketchup'")
end
else
# Insert configSketchup completely to objects.
database.exec("INSERT INTO 'objects' VALUES #{DEFAULT_CONFIG}")
end
end
# rubocop:enable Style/GuardClause
end
end
# rubocop:enable Metrics/ModuleLength
end
@@ -22,20 +22,23 @@ module SpeckleConnector
entity.attribute_dictionaries.each do |att_dict|
dict_name = att_dict == '' ? 'empty_dictionary_name' : att_dict.name
dictionaries[dict_name] = att_dict.to_h.to_json unless IGNORED_DICTIONARY_NAMES.include?(att_dict.name)
dictionaries[dict_name] = att_dict.to_h unless IGNORED_DICTIONARY_NAMES.include?(att_dict.name)
end
dictionaries
end
# @param entity [Sketchup::Entity] entity to set attribute dictionaries
# rubocop:disable Metrics/CyclomaticComplexity
def self.attribute_dictionaries_to_native(entity, dictionaries)
return if dictionaries.nil?
classification_to_native(entity, dictionaries) if entity.is_a?(Sketchup::ComponentDefinition)
dictionaries.each do |dict_name, entries|
next unless entries.is_a?(Hash)
dict_name = dict_name == 'empty_dictionary_name' ? '' : dict_name
JSON.parse(entries).each do |key, value|
entries.each do |key, value|
set_attribute(entity, key, value, dict_name)
rescue StandardError => e
puts("Failed to write key: #{key} value: #{value} to dictionary #{dict_name}")
@@ -43,6 +46,7 @@ module SpeckleConnector
end
end
end
# rubocop:enable Metrics/CyclomaticComplexity
# Classification is ComponentDefinition specific, so they can be added only definition by add_classification
# method.
@@ -51,7 +55,7 @@ module SpeckleConnector
applied_schema_types = dictionaries['AppliedSchemaTypes']
return if applied_schema_types.nil?
JSON.parse(applied_schema_types).each do |key, value|
applied_schema_types.each do |key, value|
definition_entity.add_classification(key, value)
end
end
+95 -92
View File
@@ -26,60 +26,66 @@
<!-- Switch Diffing -->
<v-switch
v-model="diffing"
:input-value="diffing"
class="pt-3 mt-n2 mb-n2"
:label="'Diffing (Alpha)'"
@click="switchDiffing"
@change="diffingHandler"
/>
<div class="sm1 mt-3">Send Strategy</div>
<v-divider class="mb-2"/>
<v-switch
v-model="combineFacesByMaterial"
class="pt-1 mt-n2 mb-n2"
:input-value="combineFacesByMaterial"
:label="'Combine faces by material under mesh'"
@change="combineFacesByMaterialHandler"
/>
<v-switch
v-model="includeAttributes"
:input-value="includeAttributes"
class="pt-1 my-n5"
:label="'Include entity attributes'"
@change="includeAttributesHandler"
/>
<v-icon class="ml-3" style="line-height: 0;">mdi-arrow-right-bottom</v-icon>
<v-switch
v-model="includeEdgeAttributes"
:input-value="includeEdgeAttributes"
class="pt-1 my-n5 ml-10"
:label="'Edge'"
:disabled="!includeAttributes"
@change="includeEdgeAttributesHandler"
/>
<v-icon class="ml-3" style="line-height: 0;">mdi-arrow-right-bottom</v-icon>
<v-switch
v-model="includeFaceAttributes"
:input-value="includeFaceAttributes"
class="pt-1 my-n5 ml-10"
:label="'Face'"
:disabled="!includeAttributes"
@change="includeFaceAttributesHandler"
/>
<v-icon class="ml-3" style="line-height: 0;">mdi-arrow-right-bottom</v-icon>
<v-switch
v-model="includeGroupAttributes"
:input-value="includeGroupAttributes"
class="pt-1 my-n5 ml-10"
:label="'Group'"
:disabled="!includeAttributes"
@change="includeGroupAttributesHandler"
/>
<v-icon class="ml-3" style="line-height: 0;">mdi-arrow-right-bottom</v-icon>
<v-switch
v-model="includeComponentAttributes"
:input-value="includeComponentAttributes"
class="pt-1 my-n5 ml-10"
:label="'Component'"
:disabled="!includeAttributes"
@change="includeComponentAttributesHandler"
/>
<div class="sm1 mt-3">Receive Strategy</div>
<v-divider class="mb-2"/>
<v-switch
v-model="mergeCoplanarFaces"
:input-value="mergeCoplanarFaces"
class="pt-1 mt-n2 mb-n2"
:label="'Merge co-planar faces on receive'"
@change="mergeCoplanarFacesHandler"
/>
</v-container>
<v-card-actions>
@@ -124,96 +130,93 @@ export default {
}
},
watch: {
'preferences': {
handler(newValue) {
this.combineFacesByMaterial = newValue.model.combine_faces_by_material
this.includeAttributes = newValue.model.include_entity_attributes
this.includeFaceAttributes = newValue.model.include_face_entity_attributes
this.includeEdgeAttributes = newValue.model.include_edge_entity_attributes
this.includeGroupAttributes = newValue.model.include_group_entity_attributes
this.includeComponentAttributes = newValue.model.include_component_entity_attributes
this.mergeCoplanarFaces = newValue.model.merge_coplanar_faces
this.diffing = newValue.user.diffing
},
deep: true,
immediate: true
},
'showSettings': {
handler(newValue) {
if (newValue){
this.$mixpanel.track('Connector Action', { name: 'Open Settings Dialog' })
}
},
deep: true
},
'combineFacesByMaterial': {
handler(newValue) {
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "combine_faces_by_material", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Combine Faces By Material Option' })
},
deep: true
},
'includeAttributes': {
handler(newValue) {
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "include_entity_attributes", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Include Entity Attributes Option' })
},
deep: true
},
'includeFaceAttributes': {
handler(newValue) {
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "include_face_entity_attributes", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Include Face Entity Attributes Option' })
},
deep: true
},
'includeEdgeAttributes': {
handler(newValue) {
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "include_edge_entity_attributes", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Include Edge Entity Attributes Option' })
},
deep: true
},
'includeGroupAttributes': {
handler(newValue) {
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "include_group_entity_attributes", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Include Group Entity Attributes Option' })
},
deep: true
},
'includeComponentAttributes': {
handler(newValue) {
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "include_component_entity_attributes", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Include Component Entity Attributes Option' })
},
deep: true
},
'mergeCoplanarFaces': {
handler(newValue) {
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "merge_coplanar_faces", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Merge Co-Planar Faces Option' })
},
deep: true
},
'diffing': {
handler(newValue) {
sketchup.exec({
name: "user_preferences_updated",
data: {preference_hash: "configSketchup", preference: "diffing", value: newValue}
})
this.$mixpanel.track('Connector Action', { name: 'Diffing' })
},
deep: true
}
}
},
methods: {
diffingHandler(newValue){
this.diffing = !!newValue
sketchup.exec({
name: "user_preferences_updated",
data: {preference_hash: "configSketchup", preference: "diffing", value: this.diffing}
})
this.$mixpanel.track('Connector Action', { name: 'Diffing' })
},
combineFacesByMaterialHandler(newValue){
this.combineFacesByMaterial = !!newValue
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "combine_faces_by_material", value: this.combineFacesByMaterial}
})
this.$mixpanel.track('Connector Action', { name: 'Combine Faces By Material Option' })
},
includeAttributesHandler(newValue){
this.includeAttributes = !!newValue
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "include_entity_attributes", value: this.includeAttributes}
})
this.$mixpanel.track('Connector Action', { name: 'Include Entity Attributes Option' })
},
includeFaceAttributesHandler(newValue){
this.includeFaceAttributes = !!newValue
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "include_face_entity_attributes", value: this.includeFaceAttributes}
})
this.$mixpanel.track('Connector Action', { name: 'Include Face Entity Attributes Option' })
},
includeEdgeAttributesHandler(newValue){
this.includeEdgeAttributes = !!newValue
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "include_edge_entity_attributes", value: this.includeEdgeAttributes}
})
this.$mixpanel.track('Connector Action', { name: 'Include Edge Entity Attributes Option' })
},
includeGroupAttributesHandler(newValue){
this.includeGroupAttributes = !!newValue
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "include_group_entity_attributes", value: this.includeGroupAttributes}
})
this.$mixpanel.track('Connector Action', { name: 'Include Group Entity Attributes Option' })
},
includeComponentAttributesHandler(newValue){
this.includeComponentAttributes = !!newValue
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "include_component_entity_attributes", value: this.includeComponentAttributes}
})
this.$mixpanel.track('Connector Action', { name: 'Include Component Entity Attributes Option' })
},
mergeCoplanarFacesHandler(newValue){
this.mergeCoplanarFaces = !!newValue
sketchup.exec({
name: "model_preferences_updated",
data: {preference: "merge_coplanar_faces", value: this.mergeCoplanarFaces}
})
this.$mixpanel.track('Connector Action', { name: 'Merge Co-Planar Faces Option' })
},
switchTheme() {
this.$vuetify.theme.dark = !this.$vuetify.theme.dark
sketchup.exec({