Compare commits

...

316 Commits

Author SHA1 Message Date
Oğuzhan Koral d6075209df Fix (Update): Check if branch instance is deleted 2023-05-12 12:53:19 +03:00
oguzhankoral 9d1b39422b Extract entities_updated method 2023-05-12 12:39:37 +03:00
oguzhankoral 38e297d32b Correct condition for is_update_commit 2023-05-12 11:31:23 +03:00
oguzhankoral 459ef3c9f1 Check if branch instance is deleted 2023-05-12 11:21:34 +03:00
Oğuzhan Koral 3bb12487ba Feat (QGIS): Attribute support for qgis commits 2023-05-12 10:13:44 +03:00
oguzhankoral 8b2428c60d Workaround for QGIS geometry objects or arrays 2023-05-10 17:26:47 +03:00
oguzhankoral f9befd9051 Write qgis attributes to components 2023-05-10 12:43:29 +03:00
oguzhankoral 1020a9bd5d Wrap PolygonElement object into component 2023-05-10 12:07:40 +03:00
Oğuzhan Koral 2844c4ea86 Fix (Instance): Disable aligning axes of displayValue components 2023-05-08 16:54:52 +03:00
oguzhankoral 3344188fee Disable aligning axes of displayValue components 2023-05-08 16:44:10 +03:00
Oğuzhan Koral 67138c2f78 Fix (Update): Update behavior for Revit > Sketchup 2023-05-08 13:21:31 +03:00
oguzhankoral f59d19ef21 Check children changed or not for upcoming blocks 2023-05-08 13:13:53 +03:00
Oğuzhan Koral d0fa7e638f Feat (Collections): Model collections 2023-05-08 09:46:44 +03:00
oguzhankoral 63011e466e Change relation speckle types to collections 2023-05-08 09:34:47 +03:00
oguzhankoral 92d1976300 Split collections into helper classes 2023-05-04 20:08:17 +03:00
oguzhankoral a0cdc9fa07 Check sketchup entities for respomding true to layer set, if so set 2023-05-04 12:22:02 +03:00
oguzhankoral 60f0006597 Remove old layer helper methods 2023-05-04 12:06:56 +03:00
oguzhankoral 67a3c62a08 Align instance axes that created from displayValues without transform 2023-05-03 18:23:58 +03:00
oguzhankoral 21d5dc1e0b Remove layer argument passing for all to_native methods 2023-04-27 17:59:39 +01:00
oguzhankoral 0d810f59f9 Place received objects into correct layers 2023-04-27 17:39:15 +01:00
oguzhankoral dba60b700f Layer relations to native layer tree 2023-04-27 17:21:20 +01:00
oguzhankoral ad9a56bd20 Remove unused methods 2023-04-27 16:16:15 +01:00
oguzhankoral 82585e9104 Convert views to native with traversal method 2023-04-27 16:14:20 +01:00
oguzhankoral dfbb241b0b Model collections on send 2023-04-27 14:56:59 +01:00
oguzhankoral f69ea91f45 Add query for layers 2023-04-26 16:44:11 +01:00
oguzhankoral 768d916092 Convert scenes to view3d on class 2023-04-26 16:44:00 +01:00
oguzhankoral c0acecae10 Query methods for direct shapes 2023-04-26 16:43:31 +01:00
oguzhankoral f741d1c7e4 Clean converter from object specific functions 2023-04-26 16:43:03 +01:00
oguzhankoral d25c70f1d1 Do not load bootstrap again when reload triggered 2023-04-26 16:42:06 +01:00
oguzhankoral 6f954467ef Move collections into related namespace 2023-04-26 16:41:37 +01:00
oguzhankoral 867987a5f5 Group meshes also according to layers 2023-04-26 14:39:52 +01:00
oguzhankoral 61e0a6351c Init collection class 2023-04-14 10:09:44 +03:00
oguzhankoral 558264c23b Add documentation to app methods 2023-04-13 13:57:37 +03:00
oguzhankoral cf7aa371af Move view related methods to related class 2023-04-12 20:04:17 +03:00
oguzhankoral ffedaf6a73 Fix bug on views receive 2023-04-12 14:02:50 +03:00
oguzhankoral d9ae272b36 Proof of concept of layer relations
# Conflicts:
#	speckle_connector/src/convertors/to_speckle.rb
#	speckle_connector/src/speckle_objects/geometry/mesh.rb
#	speckle_connector/src/speckle_objects/other/block_definition.rb
#	speckle_connector/src/speckle_objects/other/block_instance.rb
2023-04-12 12:56:59 +03:00
Oğuzhan Koral 3c5d1f5c9e Fix (References): missing namespaces 2023-04-12 11:09:03 +03:00
oguzhankoral b69b8b7585 Fix missing references after couple of implementation 2023-04-12 10:44:15 +03:00
Oğuzhan Koral 8c7e498fb2 Fix (Security): Bump sqlite3 from 5.0.8 to 5.1.5 in /ui 2023-04-10 14:42:56 +03:00
dependabot[bot] 359413a296 Bump sqlite3 from 5.0.8 to 5.1.5 in /ui
Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.0.8 to 5.1.5.
- [Release notes](https://github.com/TryGhost/node-sqlite3/releases)
- [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.0.8...v5.1.5)

---
updated-dependencies:
- dependency-name: sqlite3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-10 11:19:39 +00:00
Oğuzhan Koral cef7d894c4 Fix (Security): Bump git from 1.12.0 to 1.18.0 2023-04-10 14:15:16 +03:00
dependabot[bot] 7d9cc1aacc Bump git from 1.12.0 to 1.18.0
Bumps [git](https://github.com/ruby-git/ruby-git) from 1.12.0 to 1.18.0.
- [Release notes](https://github.com/ruby-git/ruby-git/releases)
- [Changelog](https://github.com/ruby-git/ruby-git/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ruby-git/ruby-git/compare/v1.12.0...v1.18.0)

---
updated-dependencies:
- dependency-name: git
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-10 11:12:22 +00:00
Oğuzhan Koral a6780a756b Feat (Mapper): Mapper tool for direct shapes 2023-04-10 14:05:44 +03:00
Oğuzhan Koral 4e37d17b42 Feat (Mapper): Mapped elements table and it's buttons 2023-04-10 13:48:19 +03:00
oguzhankoral 793923348f Switch button for isolate/show-all accoding to state 2023-04-10 13:37:04 +03:00
oguzhankoral 700db1788d Improve performance dramatically of isolate 2023-04-08 00:02:52 +03:00
oguzhankoral 68a5a44702 Make functional buttons for mapped elements 2023-04-07 20:16:00 +03:00
oguzhankoral 464891f5a1 Manage state of selected elements and existing elements 2023-04-07 11:33:05 +03:00
oguzhankoral d2e5db2680 Custom data table header for child tables 2023-04-06 22:59:46 +03:00
oguzhankoral a9c41d0545 Connect data relationship between parent and childeren data tables 2023-04-05 17:19:21 +03:00
oguzhankoral 0bc458d307 Connect nested data tables with their v-model 2023-04-05 13:54:05 +03:00
oguzhankoral 36c7cc285e Update vuetifyjs to 2.6.10 2023-04-05 13:52:56 +03:00
oguzhankoral f9fbd31a0f Wrap data table with container 2023-04-03 16:43:19 +03:00
oguzhankoral 8789c1f855 Extract mapped elements from mapper component 2023-04-03 15:41:04 +03:00
oguzhankoral f65073480a Apply mixed mapping name 2023-04-03 15:04:20 +03:00
oguzhankoral 157ed831a7 Shrink v-cards for map selection between component-definition 2023-03-31 23:19:19 +03:00
oguzhankoral 0799a59a15 Get info from definition about number of instances 2023-03-31 23:18:23 +03:00
Oğuzhan Koral 60ea3e83e1 Fix (DirectShape): Nested objects material 2023-03-31 19:57:46 +03:00
oguzhankoral 4b874deb50 Don't add mapped entities to layer groups 2023-03-31 19:55:12 +03:00
oguzhankoral 6726b9ad50 Fix nested material issıe 2023-03-31 19:54:29 +03:00
Oğuzhan Koral 75e6fe609e Fix (Mapper): Direct shape objects on viewer 2023-03-31 15:28:05 +03:00
oguzhankoral b40d1ccf7d Send directShapes collection as detached 2023-03-31 15:26:13 +03:00
Oğuzhan Koral 4f7ba23904 Feat (Mapper): Enable multiple mapping apply and clear 2023-03-31 14:33:41 +03:00
oguzhankoral 49084706f2 Enable multiple mapping apply and clear 2023-03-31 14:31:49 +03:00
Oğuzhan Koral 1852e5f6bf Chore (Mapper): Show default values for selected entity 2023-03-30 20:33:59 +03:00
oguzhankoral be0ed5428a Apply default values from selected entity 2023-03-30 20:31:16 +03:00
oguzhankoral f428efde6a Mark mapped cards and make active one 2023-03-30 19:56:49 +03:00
oguzhankoral e79f04f33e Remove unnecessary statement 2023-03-30 15:59:33 +03:00
oguzhankoral 04db8c281f Remove selected rows which was unneccessary 2023-03-30 15:59:03 +03:00
oguzhankoral 2681698186 Disable definition option for groups
- This is uncessary since each group has it's own unique definition on sketchup
2023-03-30 15:49:04 +03:00
Oğuzhan Koral 1682c66da6 Feat (Mapper): Read mapped elements on document load 2023-03-29 23:20:29 +03:00
oguzhankoral 38bc1032d8 Read mapped entities on document load model 2023-03-29 23:18:05 +03:00
Oğuzhan Koral ba37eef9ca Feat (Mapper): Group meshes also for base geometries of direct shapes 2023-03-29 22:49:30 +03:00
oguzhankoral 3db179882a Group direct shape meshes by material 2023-03-29 22:45:45 +03:00
oguzhankoral ba8f25a807 Move mesh related methods from definition 2023-03-29 22:44:06 +03:00
oguzhankoral eac3d3089b Find entities on active path 2023-03-29 22:43:13 +03:00
Oğuzhan Koral 0454a9e147 Fix (Mapper): Group global transformations for direct shapes 2023-03-29 21:03:13 +03:00
oguzhankoral f390cd1c68 Fix/disable rubocop issues 2023-03-29 21:02:49 +03:00
oguzhankoral aaa1ba5aa9 Apply parent's material to direct shape faces 2023-03-29 20:58:57 +03:00
oguzhankoral fb9556abd9 Fix sub elements transform 2023-03-29 20:58:35 +03:00
Oğuzhan Koral 3cfb3b1f84 Feat (Mapper): Show mapped elements on table 2023-03-29 14:54:21 +03:00
oguzhankoral b071bab137 Collect mapped entities when mapper mounted 2023-03-29 14:49:48 +03:00
oguzhankoral 5dbc68ea76 State management for mapped elements 2023-03-29 14:45:09 +03:00
oguzhankoral e76fbc3b77 Notify user after apply or clear mapping 2023-03-29 13:12:59 +03:00
oguzhankoral 09ca0514d1 Remove mapping word from buttons 2023-03-29 12:57:12 +03:00
oguzhankoral faec32a5bb Remove attribute dictionary completely if mappings cleared 2023-03-29 12:35:56 +03:00
oguzhankoral 1f5793ab79 Nil check for reading objects from scratch 2023-03-29 11:50:03 +03:00
oguzhankoral df22bd1cec Fallback to definition schema of instance 2023-03-29 11:47:21 +03:00
oguzhankoral 61dba4e78d Return false if entities are missing for base_and_entities 2023-03-29 11:44:58 +03:00
Oğuzhan Koral bb678117f8 Feat (Mapper): UI communication with Sketchup 2023-03-28 21:36:12 +03:00
oguzhankoral 2dc08e71b7 Collect also instances that definitions' mapped 2023-03-28 21:02:21 +03:00
oguzhankoral df2f844e74 Styles for entity selection to map 2023-03-28 20:11:52 +03:00
oguzhankoral 621c602fc0 Make narrower scrool bar 2023-03-28 20:11:15 +03:00
oguzhankoral f809169757 Fetch definition schema from selection 2023-03-28 20:10:59 +03:00
oguzhankoral 848b135612 Clear mappings 2023-03-28 20:10:26 +03:00
oguzhankoral db6af66705 Fill inputs according to selection 2023-03-28 02:34:55 +03:00
oguzhankoral 728e1f5a86 Send categories as key-value pair objects 2023-03-28 02:34:38 +03:00
oguzhankoral d02d95bc9e Apply mapping command and action 2023-03-28 02:34:13 +03:00
oguzhankoral c1180c5373 Trigger sketchup action to apply mappings 2023-03-28 01:49:17 +03:00
oguzhankoral 4e01fb64e6 Track objects from selection table and prepare mapping 2023-03-28 01:46:21 +03:00
oguzhankoral d0fcb1da34 Add theme colors for mapper active entity cards 2023-03-28 01:45:44 +03:00
oguzhankoral 48352d06b3 Extend selected entity data from sketchup 2023-03-28 01:45:18 +03:00
oguzhankoral d995652569 Get info from last selected entity 2023-03-27 18:57:42 +03:00
oguzhankoral 5a958fa51c Split class names with space 2023-03-27 18:57:13 +03:00
oguzhankoral cfb9e2cacd Print mapped count to selection table 2023-03-27 18:12:08 +03:00
oguzhankoral 7932fc1cab Send entity persistent_id to UI 2023-03-27 18:11:40 +03:00
oguzhankoral e0fc1715b5 Create data table for selection 2023-03-27 14:54:19 +03:00
oguzhankoral 1cfd6caa64 Read object schemas from selection with their type 2023-03-27 14:54:01 +03:00
oguzhankoral 54bf18888d Helper method definition to group objects by property value 2023-03-27 14:53:23 +03:00
Oğuzhan Koral 6b60fc4259 Feat (Observers): Selection observers to collect data 2023-03-24 19:37:20 +03:00
oguzhankoral 216af8697c Create accordion for mapper 2023-03-24 17:33:42 +03:00
oguzhankoral d97e7314d0 Add categories to fake data 2023-03-24 17:33:11 +03:00
oguzhankoral 67d9dda143 Sample interactions 2023-03-24 13:06:55 +03:00
oguzhankoral 5c60e303cb Move mapped_with_schema method to reader 2023-03-24 12:36:45 +03:00
oguzhankoral e36b8f3a56 Create selection observer to trigger UI 2023-03-24 12:36:21 +03:00
Oğuzhan Koral 1e7a19b463 Chore (UI): UI components for mapping tool 2023-03-24 12:30:55 +03:00
oguzhankoral 8004249a29 Init mapper with tab 2023-03-23 16:08:36 +03:00
Oğuzhan Koral 6cd800e4ef Feat (Mapping): Send direct shape PoC 2023-03-23 12:54:42 +03:00
oguzhankoral e182bc7ed2 Clean unused methods 2023-03-22 21:38:23 +03:00
oguzhankoral c338d51c9d Skip faces into definitions if it is mapped 2023-03-22 21:19:11 +03:00
oguzhankoral c1912250df Do definition check in advance and that's why definitions argument removed 2023-03-22 21:18:50 +03:00
oguzhankoral 994c41980e Init definitions and render_materials for later caching 2023-03-22 21:18:03 +03:00
oguzhankoral 385b7a514d Transform mesh if global_transformation defined 2023-03-22 21:17:25 +03:00
oguzhankoral 3aaade9228 Entity queries for parental relationships 2023-03-22 21:16:58 +03:00
oguzhankoral b4dd19b711 Split direct shape conversion from generic conversion 2023-03-22 21:06:53 +03:00
oguzhankoral 9e9b83b4ba Init direct shape definition 2023-03-22 21:06:14 +03:00
oguzhankoral 5ff5114669 Add revit categories dictionary 2023-03-21 19:55:44 +03:00
oguzhankoral f63039f3fb Change module name with revit specific 2023-03-21 19:53:47 +03:00
oguzhankoral b39399463d Add speckle schema to objects for to speckle 2023-03-21 14:17:36 +03:00
oguzhankoral 8217ef7fbb Create speckle schema dictionary handler 2023-03-21 14:17:18 +03:00
oguzhankoral b4851c34e1 Separate base dictionary_handler from root 2023-03-21 14:16:52 +03:00
Oğuzhan Koral 8e39832d3e Fix (Instancing): Definition uniqueness for mirrored definitions 2023-03-20 22:14:10 +03:00
oguzhankoral ce7ac6da16 Create definitions with it's id 2023-03-20 22:11:52 +03:00
oguzhankoral 57879a57cd Enable conversion for full assembly name block instance 2023-03-20 22:05:42 +03:00
Oğuzhan Koral 6f5b367028 Fix (Revit): Do not create layers in advance for revit receives 2023-03-20 13:04:42 +03:00
oguzhankoral 291948c1ef Do not create layers in advance for revit receives 2023-03-20 12:57:26 +03:00
Oğuzhan Koral 0ca6888fb1 Fix (Instancing): Support nested families re-fix 2023-03-20 10:56:50 +03:00
oguzhankoral 5e92236396 Remove skp attributes from transform 2023-03-20 10:45:45 +03:00
oguzhankoral 28db40112b Collect revit elements also into displayValue to handle later 2023-03-20 10:45:13 +03:00
Oğuzhan Koral b2153883ef Revert "Fix (Instancing): Support nested families" 2023-03-16 18:28:44 +03:00
Oğuzhan Koral 807cbeb75d Revert "Fix (Instancing): Support nested families" 2023-03-16 18:28:14 +03:00
Oğuzhan Koral 4730aebdc6 Fix (Instancing): Support nested families 2023-03-16 18:06:50 +03:00
oguzhankoral c2279eec87 Add elements of revit_definition to display value to handle all 2023-03-16 18:03:21 +03:00
Oğuzhan Koral f5567ae9ad Chore (Mixpanel): Send isMultiplayer parameter with Receive action 2023-03-16 11:50:45 +03:00
oguzhankoral 2fcba04cf5 Pass sourceHostApp with slug and sourceHostAppVersion full info on commit
For "Revit2022",
sourceHostApp: "revit"
sourceHostAppVersion: "Revit2022"
2023-03-16 11:13:49 +03:00
oguzhankoral 0eefd1605f Add static HostApplication instances per connector
- This helps to retrieve name and slug of the connector
2023-03-16 11:12:29 +03:00
oguzhankoral 7b88ea022d Send isMultiplayer parameter with Receive action 2023-03-15 12:35:57 +03:00
Oğuzhan Koral 7a922114ab Fix (Instancing): Split revit instance from block instance 2023-03-14 21:18:22 +03:00
oguzhankoral f717c270fd Collect views according to speckle_type 2023-03-14 21:16:56 +03:00
oguzhankoral ee5ed468c3 Fix rubocop issues 2023-03-14 20:18:28 +03:00
oguzhankoral a9ded445a7 Separate revit instance from block instance 2023-03-14 20:08:02 +03:00
oguzhankoral 9ecad52df2 Dont create speckle entity for sketchup materials 2023-03-14 17:48:20 +03:00
oguzhankoral 8c500985bf Flip soft edge check with hard edge 2023-03-14 16:37:16 +03:00
Oğuzhan Koral 3567f9ba5d Fix (Layers): Update level object's layer to Levels 2023-03-14 14:42:01 +03:00
oguzhankoral 852bfb716c Update level object's layer to Levels 2023-03-14 14:39:19 +03:00
Oğuzhan Koral 473956aa3c Fix (Update): Update behavior 2023-03-14 11:33:25 +03:00
oguzhankoral b22fc19400 Register component and groups by default for update behavior 2023-03-14 11:31:17 +03:00
oguzhankoral f6a8ece992 Check existing scene names to prevent duplications 2023-03-14 11:28:28 +03:00
oguzhankoral afc0c21aae Swap branch and stream name for commit obj 2023-03-14 11:10:05 +03:00
Oğuzhan Koral 08e6106c1f Fix (Levels): Levels should be visible 2023-03-13 14:20:01 +03:00
oguzhankoral 1f9f654e3d Add levels as cline 2023-03-13 14:11:10 +03:00
oguzhankoral 501e923760 Add section planes into received component
By this way these section planes will be active only in this commit objects
2023-03-13 09:58:32 +03:00
Oğuzhan Koral 37b716cfc5 Feat (Scene): Create scenes from revit views 2023-03-12 23:46:14 +03:00
oguzhankoral 8584db0ab5 Create scenes from revit views 2023-03-12 23:41:00 +03:00
Oğuzhan Koral 5aff3e9692 Feat (Instancing): Definition naming convention 2023-03-10 18:06:48 +03:00
oguzhankoral 5d8f03cbc3 Fix rubocop suggestions 2023-03-10 18:04:14 +03:00
oguzhankoral b6c85ac15a Group received objects with branch and stream name 2023-03-10 17:57:43 +03:00
oguzhankoral c2af757f4e Collect received objects under component 2023-03-10 16:08:17 +03:00
oguzhankoral 26252c55ac Pass branch and stream name to to_native converter 2023-03-10 16:07:34 +03:00
oguzhankoral 3ce547bcea Update naming convention 2023-03-09 19:04:01 +03:00
oguzhankoral e0492f589e Update will be soften list 2023-03-09 19:03:49 +03:00
oguzhankoral 4931288ffd Update will be hidden layers list 2023-03-09 19:03:26 +03:00
oguzhankoral 52e0f77a3c Remove category from definition names 2023-03-09 16:12:47 +03:00
Oğuzhan Koral 20c016f1bf Feat (Layers): revit categories as tags 2023-03-09 14:24:47 +03:00
oguzhankoral 00a271cbba Create tags for missing categories 2023-03-09 14:24:08 +03:00
oguzhankoral 3a6b69634d Hide specific revit layers
- Rooms, Mass, Mass Floor
2023-03-09 13:36:39 +03:00
Oğuzhan Koral 78cf7f42e3 Feat (Instancing): Support instancing 2023-03-08 23:56:53 +03:00
oguzhankoral 2b52454c00 Extract collect_definition_geometries for display value 2023-03-08 23:56:10 +03:00
oguzhankoral e032e4561b Receive unique elements with element id even if they have same type 2023-03-08 23:41:42 +03:00
oguzhankoral 23ce1e0476 Make soft edge according to definition name 2023-03-08 23:35:47 +03:00
oguzhankoral 825b7b98d7 Fix rubocop suggestions 2023-03-08 23:35:26 +03:00
oguzhankoral 3de3128a2a Cover mullions by including their elements 2023-03-08 23:34:56 +03:00
oguzhankoral 92d70ccc8b Support Objects.Other.Revit.RevitInstance 2023-03-08 23:33:14 +03:00
oguzhankoral f8c5154457 Remove TransparencySort option from model
- It does not make sense anymore
2023-03-08 23:32:35 +03:00
Oğuzhan Koral 3a3874e9f1 Feat (RevitLevels): Revit levels to Sketchup section planes 2023-03-08 20:31:51 +03:00
oguzhankoral 61b94c519b Update TransparencySort value to 3 by default 2023-03-08 14:39:58 +03:00
oguzhankoral ac0f46ca66 Update unique definitions for revit objects 2023-03-08 14:17:05 +03:00
oguzhankoral e538cdb509 Create levels from objects 2023-03-08 14:16:46 +03:00
oguzhankoral df8a63c5f5 By default set DisplaySectionPlanes is true 2023-03-08 14:16:23 +03:00
oguzhankoral 6827a61bf8 Simplify convert_to_speckle_entities logic 2023-03-08 12:32:52 +03:00
Oğuzhan Koral 20e56c05b3 Feat (Receive): Receive objects as speckle entity 2023-03-07 15:30:59 +03:00
oguzhankoral 5494139319 Disable rubocop issues 2023-03-07 15:28:25 +03:00
oguzhankoral c491ba087a Implement another user setting to activate continuous traversal 2023-03-07 15:17:38 +03:00
oguzhankoral 5cf045c941 Collect speckle materials from sketchup model 2023-03-07 15:17:16 +03:00
oguzhankoral 55fea5f077 Add definitions as collection 2023-03-07 15:16:47 +03:00
oguzhankoral 3a9cb73950 Implement dynamic to_native methods to objects 2023-03-07 15:14:12 +03:00
oguzhankoral d99f613b79 Return combined faces from clean_up 2023-03-07 15:13:33 +03:00
oguzhankoral 68db8ddff3 Add type constants 2023-03-07 15:12:50 +03:00
oguzhankoral c7490b264b Pass stream id to converter by default 2023-03-07 15:11:34 +03:00
Oğuzhan Koral a8e84cfad3 Chore (Query): Query source application from commit 2023-02-28 16:03:56 +03:00
oguzhankoral 5ec4f7e4e8 Pass source application info to command and action 2023-02-28 15:54:04 +03:00
oguzhankoral b019ce2f2e Update stream query for commit sourceApplication 2023-02-28 15:53:41 +03:00
Oğuzhan Koral dd90e41610 Fix (Instance): Component instance naming 2023-02-27 10:43:20 +03:00
oguzhankoral bdf78e374d Update diffing icon with eye instead triangle 2023-02-27 10:27:16 +03:00
oguzhankoral 163197f583 Do not set speckle id as instance name as fallback
Now as is
2023-02-27 10:26:53 +03:00
oguzhankoral 8e13bba44a Write always entity dictionaries while converting
This was tightly coupled with diffing before, now it is separated. It's better.
2023-02-27 10:26:03 +03:00
Oğuzhan Koral e4478b86a5 Feat (SpeckleEntity): Read speckle entities when model loaded 2023-02-27 09:01:12 +03:00
oguzhankoral 1b1ae59ed7 Read speckle entities from sketchup model 2023-02-24 19:18:16 +03:00
oguzhankoral a54f44d22a Update init method of speckle entity with more general way 2023-02-24 19:18:03 +03:00
oguzhankoral d2394274eb Simplify speckle entity object
- So entity specific objects removed
2023-02-24 19:17:18 +03:00
Oğuzhan Koral 0eedb0b397 Chore (Mixpanel): Mixpanel alignment 2023-02-24 10:59:45 +03:00
oguzhankoral 782e9db1f6 Align mixpanel event names 2023-02-24 10:10:50 +03:00
Oğuzhan Koral 2977472c56 Feat (Preferences): Validate model and user preferences 2023-02-22 13:02:18 +03:00
oguzhankoral 8f3a8bfa91 Validate user and model preferences while reading 2023-02-22 12:53:26 +03:00
Oğuzhan Koral 914f6e9516 Fix (Model): Update preferences on state and UI when model has changed 2023-02-21 13:01:53 +03:00
oguzhankoral fa6f65cb7c Use handler methods instead watchers per switch 2023-02-21 12:57:24 +03:00
oguzhankoral dafbe6f995 Update preferences on state and UI when model has changed 2023-02-21 10:47:49 +03:00
Oğuzhan Koral 4a8ac6377b Fix (Preferences): Validate preferences if data is incomplete 2023-02-20 16:47:55 +03:00
oguzhankoral c91fac4ec4 Validate preferences if data is incomplete 2023-02-20 16:30:46 +03:00
Oğuzhan Koral 00d23b084e Chore (Attributes): Send receive attributes as deserialized 2023-02-20 14:58:21 +03:00
oguzhankoral e0a3b82cea Send receive attributes as deserialized 2023-02-20 14:56:01 +03:00
Oğuzhan Koral e49036d8c4 Chore (Attributes): Send and receive ifc classifications 2023-02-20 13:36:52 +03:00
oguzhankoral 4a8356692b Send and receive classifications for component definitions 2023-02-20 13:30:31 +03:00
Oğuzhan Koral adfb7bc63a Fix (BlockDefinition): align detach properties 2023-02-20 12:44:35 +03:00
oguzhankoral 3f41cefa88 Remove basePoint from definition 2023-02-20 12:38:57 +03:00
oguzhankoral 2074ff1987 Rename blockDefinition to definition for BlockInstance 2023-02-20 11:18:50 +03:00
Oğuzhan Koral 65fe189421 Feat (Views): Expand scene support 2023-02-17 22:27:43 +03:00
oguzhankoral bb7590eab4 Simplify sending rendering_options 2023-02-17 22:22:26 +03:00
oguzhankoral 6a04457219 Send and receive rendering options of scenes 2023-02-17 21:51:44 +03:00
oguzhankoral 7f8b1d9586 Include update properties to scenes 2023-02-17 15:39:00 +03:00
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
Oğuzhan Koral d2198c0765 Improve refreshMixpanelIds function with better registration 2023-02-17 02:16:28 +03:00
oguzhankoral 549bf63198 Improve refreshMixpanelIds function with better registration and identifying 2023-02-16 16:26:00 +03:00
Oğuzhan Koral 8b824f5342 Update innosetup workflow
There were issues with innosetup workflow which does not run one powershell.exe,
so workflows splitted into chunks according to shells
2023-02-08 21:20:17 +03:00
oguzhankoral d052d5e8a1 Change set env variable to run with powershell 2023-02-08 21:12:00 +03:00
oguzhankoral 610c22dd02 Update innosetup workflow 2023-02-08 21:05:02 +03:00
Oğuzhan Koral a5496ab6a9 Feat (CI): Run CI step to create env variables for innosetup 2023-02-08 20:14:23 +03:00
oguzhankoral c29c8f009c Remove duplicated context: innosetup on yaml file 2023-02-02 10:34:34 +02:00
oguzhankoral fb7e9f2a6c Run CI step to create env variables for innosetup 2023-02-01 23:42:25 +02:00
Oğuzhan Koral 62c2bbb9fa Chore (Notification): Add error notification to GlobalToast 2023-01-23 15:34:03 +03:00
oguzhankoral 7656772194 Add error notification to GlobalToast 2023-01-23 15:31:13 +03:00
Oğuzhan Koral f22ff050e0 Fix (Stream, Attributes): Follow up fixes add stream by url and attributes 2023-01-23 12:35:22 +03:00
oguzhankoral 708f0b44fd Check face entity attributes settings before merging faces into mesh 2023-01-23 12:01:47 +03:00
oguzhankoral 2ee4581f17 Check stream role is null first before split 2023-01-23 12:00:13 +03:00
Oğuzhan Koral e337fb869f Feat (Settings) Advanced settings for entity specific attributes 2023-01-22 00:09:05 +03:00
oguzhankoral 4a8b0147e1 Create UI components for entity specific settings 2023-01-22 00:03:18 +03:00
oguzhankoral c95a1c7e1f Consider entity settings on speckle objects 2023-01-22 00:02:51 +03:00
oguzhankoral a07cd5c3f5 Extend model preferences with entity specific settings 2023-01-22 00:02:24 +03:00
Oğuzhan Koral 4d1473582e Feat (Stream): Add streams by URL or id 2023-01-21 17:33:35 +03:00
oguzhankoral 3bc9f4c452 Fix rubocop issue 2023-01-21 17:33:15 +03:00
oguzhankoral 11377038a0 Implement and make functional adding streams by url 2023-01-20 22:26:36 +03:00
Oğuzhan Koral 6053d3eac1 Fix (Attributes): Rescue from problematic dictionaries 2023-01-20 11:50:18 +03:00
oguzhankoral 529830f36b Rescue from problematic dictionaries 2023-01-20 11:47:34 +03:00
Oğuzhan Koral 505cf6265c Feat (Scene): Send and receive scenes 2023-01-19 20:09:19 +03:00
oguzhankoral 8cd9673eec Send scenes to speckle 2023-01-19 20:05:44 +03:00
oguzhankoral ac9cb28558 Receive named views as scene 2023-01-19 16:38:32 +03:00
Oğuzhan Koral 6eefe0698c Fix (Branch): Activate created branch 2023-01-19 12:17:58 +03:00
oguzhankoral d0113532b6 Activate created branch 2023-01-19 11:09:23 +03:00
Oğuzhan Koral 5a1d2ad5f4 Feat (Branch): Create branch button
Thanks Fabians for helps
2023-01-18 17:02:20 +03:00
oguzhankoral dfe02f4c74 Rename CreateBranchDialog 2023-01-18 16:58:05 +03:00
oguzhankoral 5349d556b9 Group dialogs to folder 2023-01-18 16:21:43 +03:00
oguzhankoral 7f7d8a501b Add notes about refresh 2023-01-18 15:58:43 +03:00
oguzhankoral cbee0e465b Remove unused combineFacesByMaterialHandler method 2023-01-18 15:44:20 +03:00
oguzhankoral faf00f0b0d Refetch stream on StreamCard whenever branch created 2023-01-18 15:42:33 +03:00
oguzhankoral fa97da5781 Track create stream and branch via mixpanel 2023-01-18 15:03:14 +03:00
oguzhankoral e7bab546db Remove loggings 2023-01-18 14:16:52 +03:00
oguzhankoral e2f4a30b5b Fix updating problem on created branch
- Thanks to Fabians
2023-01-18 11:53:50 +03:00
oguzhankoral ccff1df041 Add tooltip to create branch method 2023-01-18 11:53:11 +03:00
oguzhankoral 2037cfc25a Import and use CreateBranchDialog per stream card 2023-01-17 19:24:53 +03:00
oguzhankoral 268a091d8a Add dialog for branch creation 2023-01-17 19:23:44 +03:00
Oğuzhan Koral 6b52dfab3e Chore (Theming): Store light mode setting separately 2023-01-17 16:16:39 +03:00
oguzhankoral 839999851f Correct preference hash for theming 2023-01-17 16:12:21 +03:00
oguzhankoral a87470b7b5 Split Sketchup settings 2023-01-17 16:11:56 +03:00
123 changed files with 6073 additions and 983 deletions
+33 -11
View File
@@ -36,19 +36,41 @@ jobs:
- attach_workspace:
at: ./
- run:
name: Patch
name: Create Innosetup signing cert
shell: powershell.exe
command:
| # If no tag, use 0.0.0.1 and don't make any YML (for testing only!)
$tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "0.0.0" } else { $env:CIRCLE_TAG }
$semver = if($tag.Contains('/')) {$tag.Split("/")[1] } else { $tag }
command: |
echo $env:PFX_B64 > "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.txt"
certutil -decode "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.txt" "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.pfx"
- run:
name: Set Environment Variable
shell: powershell.exe
command: |
$tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "2.0.999" } else { $env:CIRCLE_TAG }
$semver = if($tag.Contains('/')) {$tag.Split("/")[0] } else { $tag }
$ver = if($semver.Contains('-')) {$semver.Split("-")[0] } else { $semver }
$channel = if($semver.Contains('-')) {$semver.Split("-")[1] } else { "latest" }
$version = "$($ver).$($env:CIRCLE_BUILD_NUM)"
New-Item -Force "speckle-sharp-ci-tools/Installers/sketchup/$channel.yml" -ItemType File -Value "version: $semver"
echo $version
$version = "$($ver).$($env:WORKFLOW_NUM)"
python patch_version.py $semver
speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss
environment:
WORKFLOW_NUM: << pipeline.number >>
- run:
name: Build Installer
command: speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss /Sbyparam=$p
shell: cmd.exe #does not work in powershell
#- run:
# name: Patch
# shell: powershell.exe
# command:
# | # If no tag, use 0.0.0.1 and don't make any YML (for testing only!)
# $tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "0.0.0" } else { $env:CIRCLE_TAG }
# $semver = if($tag.Contains('/')) {$tag.Split("/")[1] } else { $tag }
# $ver = if($semver.Contains('-')) {$semver.Split("-")[0] } else { $semver }
# $channel = if($semver.Contains('-')) {$semver.Split("-")[1] } else { "latest" }
# $version = "$($ver).$($env:CIRCLE_BUILD_NUM)"
# New-Item -Force "speckle-sharp-ci-tools/Installers/sketchup/$channel.yml" -ItemType File -Value "version: $semver"
# echo $version
# python patch_version.py $semver
# speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss
- persist_to_workspace:
root: ./
paths:
@@ -115,7 +137,6 @@ workflows:
only: /.*/
- build-connector:
context: innosetup
slug: sketchup
requires:
- get-ci-tools
@@ -123,6 +144,7 @@ workflows:
filters:
tags:
only: /.*/
context: innosetup
- deploy-manager2:
context: do-spaces-speckle-releases
+3 -4
View File
@@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.1)
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
axiom-types (0.1.1)
@@ -26,7 +26,7 @@ GEM
path_expander (~> 1.0)
ruby_parser (~> 3.1, > 3.1.0)
sexp_processor (~> 4.8)
git (1.12.0)
git (1.18.0)
addressable (~> 2.8)
rchardet (~> 1.8)
ice_nine (0.11.2)
@@ -48,8 +48,7 @@ GEM
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
psych (3.3.4)
public_suffix (5.0.0)
public_suffix (5.0.1)
rainbow (3.1.1)
rake (13.0.6)
rchardet (1.8.0)
+3 -1
View File
@@ -15,7 +15,9 @@ module SpeckleConnector
def self.reload
load(__FILE__)
pattern = File.join(__dir__, '**/*.rb')
Dir.glob(pattern).each { |file| load(file) }
# TODO: Here is a opportunity to improve reloading process.
# We can cache last edited time of the each file later to check which file need to be reloaded.
Dir.glob(pattern).each { |file| load(file) unless file.include?('bootstrap') }
.size
end
# rubocop:enable SketchupSuggestions/FileEncoding
@@ -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,66 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'mapped_entities_updated'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Apply mappings for selected entities.
class ApplyMappings < Action
def initialize(entities_to_map, method, category, name, is_definition)
super()
@entities_to_map = entities_to_map
@method = method
@category = category
@name = name
@is_definition = is_definition
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/MethodLength
def update_state(state)
sketchup_model = state.sketchup_state.sketchup_model
entities = if sketchup_model.active_path.nil?
sketchup_model.entities
else
sketchup_model.active_path.last.definition.entities
end
# Collect entities from entity ids that comes from UI as list
entities_to_map = entities.select { |e| @entities_to_map.include?(e.persistent_id) }
# Switch to definitions if all entities are component instance and UI flag shows that
if entities_to_map.all? { |e| e.is_a?(Sketchup::ComponentInstance) } && @is_definition
entities_to_map = entities_to_map.collect(&:definition).uniq
end
# Store speckle state to update with mapped entities.
speckle_state = state.speckle_state
entities_to_map.each do |entity|
name = if @name == '<Mixed>'
entity.respond_to?(:name) ? entity.name : ''
else
@name
end
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.set_attribute(entity, :category, @category)
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.set_attribute(entity, :name, name)
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.set_attribute(entity, :method, @method)
speckle_state = speckle_state.with_mapped_entity(entity)
end
new_state = MappedEntitiesUpdated.update_state(state.with_speckle_state(speckle_state))
Events::SelectionEventAction.update_state(new_state, { apply: true })
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/MethodLength
end
end
end
@@ -0,0 +1,52 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'mapped_entities_updated'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Clear mappings for selected entities.
class ClearMappings < Action
def initialize(entities_to_map, is_definition)
super()
@entities_to_map = entities_to_map
@is_definition = is_definition
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def update_state(state)
sketchup_model = state.sketchup_state.sketchup_model
entities = if sketchup_model.active_path.nil?
sketchup_model.entities
else
sketchup_model.active_path.last.definition.entities
end
# Collect entities from entity ids that comes from UI as list
entities_to_map = entities.select { |e| @entities_to_map.include?(e.persistent_id) }
# Switch to definitions if all entities are component instance and UI flag shows that
if entities_to_map.all? { |e| e.is_a?(Sketchup::ComponentInstance) } && @is_definition
entities_to_map = entities_to_map.collect(&:definition).uniq
end
# Store speckle state to update with mapped entities.
speckle_state = state.speckle_state
entities_to_map.each do |entity|
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.remove_dictionary(entity)
speckle_state = speckle_state.with_removed_mapped_entity(entity)
end
new_state = MappedEntitiesUpdated.update_state(state.with_speckle_state(speckle_state))
Events::SelectionEventAction.update_state(new_state, { clear: true })
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
end
end
end
@@ -0,0 +1,34 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'mapped_entities_updated'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Clear mappings for selected entities from mapped elements table.
class ClearMappingsFromTable < 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)
# Flat entities to clear mappings
flat_entities = SketchupModel::Query::Entity.flat_entities(state.sketchup_state.sketchup_model.entities)
# Collect entity ids to clear mappings
entity_ids = data.collect { |_, entities| entities['selectedElements'].collect { |e| e['entityId'] } }.flatten
# Store speckle state to update with mapped entities.
speckle_state = state.speckle_state
flat_entities.each do |entity|
next unless entity_ids.include?(entity.persistent_id)
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.remove_dictionary(entity)
speckle_state = speckle_state.with_removed_mapped_entity(entity)
end
new_state = MappedEntitiesUpdated.update_state(state.with_speckle_state(speckle_state))
Events::SelectionEventAction.update_state(new_state, { clear: true })
end
end
end
end
@@ -0,0 +1,20 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Action to collect versions from sketchup and connector to track user's version by mixpanel.
class CollectVersions < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
versions = {
sketchup: Sketchup.version.to_i,
speckle: SpeckleConnector::CONNECTOR_VERSION
}
state.with_add_queue('collectVersions', versions.to_json, [])
end
end
end
end
@@ -0,0 +1,26 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Deactivate diffing.
class DeactivateDiffing < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
puts 'Diffing deactivated!'
speckle_entities = state.speckle_state.speckle_entities
diffing_activated_speckle_entities = speckle_entities.reject do |_id, entity|
entity.active_diffing_stream_id.nil?
end
diffing_activated_speckle_entities.each do |id, entity|
new_entity = entity.deactivate_diffing
speckle_entities = speckle_entities.put(id, new_entity)
end
new_speckle_state = state.speckle_state.with_speckle_entities(speckle_entities)
state.with_speckle_state(new_speckle_state)
end
end
end
end
@@ -0,0 +1,56 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../load_sketchup_model'
require_relative '../collect_preferences'
module SpeckleConnector
module Actions
module Events
# Handle events that are triggered by the {AppObserver}.
class AppEventAction < EventAction
# Handle loading new or existing model
class OnNewOrChangedModel
# Handle events when the new or existing model is loaded in Sketchup
# @param state [States::State] the current state of speckle application
# @param event_data [Array<(Sketchup::Model)>] the event data for the given event. It consists of
# a double array with a single element that is the {Sketchup::Model} object of the loaded model.
def self.update_state(state, event_data)
return state unless event_data&.any?
model = event_data.flatten.first
# LoadSketchupModel action should be responsible to update all model specific data for state and then
# should notify the UI to update it's components.
new_state = Actions::LoadSketchupModel.update_state(state, model)
# Action to let UI to render itself with new preferences state
# TODO: Later UI should be updated if any stream is invalid after
# we collected speckle_entities appropriately
CollectPreferences.update_state(new_state, {})
end
end
# Run actions that are needed before the sketchup quits
class OnQuit
# Handle when Sketchup application closes
# @param state [States::State] the current state of speckle application
# @param _event_data [Array] the event data
# @return [States::State] the transformed state object
def self.update_state(state, _event_data)
state
end
end
# Handlers that are used to handle specific events
ACTIONS = {
onNewModel: OnNewOrChangedModel,
onOpenModel: OnNewOrChangedModel,
onQuit: OnQuit
}.freeze
def self.actions
ACTIONS
end
end
end
end
end
@@ -0,0 +1,84 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../../sketchup_model/utils/face_utils'
require_relative '../../constants/dict_constants'
module SpeckleConnector
module Actions
module Events
# Event actions related to entities.
class EntitiesEventAction < EventAction
# Event action when element added.
class OnElementAdded
# @param state [States::State] the current state of the SpeckleConnector Application
def self.update_state(state, event_data)
modified_entities = event_data.to_a.collect { |e| e[1] }
# do not copy speckle base object specific attributes, because they are entity specific
modified_entities.each { |entity| entity.delete_attribute(SPECKLE_BASE_OBJECT) }
state
end
end
# Event action when element modified.
class OnElementModified
# @param state [States::State] the current state of the SpeckleConnector Application
def self.update_state(state, event_data)
speckle_state = state.speckle_state
modified_entity = event_data[0][1]
if modified_entity.is_a?(Sketchup::Face)
path = state.sketchup_state.sketchup_model.active_path
modified_faces = SketchupModel::Utils::FaceUtils.near_faces(modified_entity.edges)
path_objects = path.nil? ? [] : path + path.collect(&:definition)
parent_ids = path_objects.collect(&:persistent_id)
ids_to_invalidate = modified_faces.collect(&:persistent_id) + parent_ids
entities_to_invalidate = speckle_entities_to_invalidate(speckle_state, ids_to_invalidate)
new_speckle_state = invalidate_speckle_entities(speckle_state, entities_to_invalidate)
# This is the place we can send information to UI for diffing check
diffing = state.user_state.preferences[:user][:diffing]
new_speckle_state = new_speckle_state.with_invalid_streams_queue if diffing
return state.with_speckle_state(new_speckle_state)
end
state
end
# @param speckle_state [States::SpeckleState] the current state of the Speckle
def self.speckle_entities_to_invalidate(speckle_state, ids)
speckle_state.speckle_entities.to_h.select { |id, _| ids.include?(id) }
end
# @param speckle_state [States::SpeckleState] the current state of the Speckle
def self.invalidate_speckle_entities(speckle_state, entities_to_invalidate)
speckle_entities = speckle_state.speckle_entities
entities_to_invalidate.each do |id, speckle_entity|
edited_speckle_entity = speckle_entity.with_invalid
speckle_entities = speckle_entities.put(id, edited_speckle_entity)
end
speckle_state.with_speckle_entities(speckle_entities)
end
end
# Event action when element removed.
class OnElementRemoved
# @param state [States::State] the current state of the SpeckleConnector Application
def self.update_state(state, _event_data)
# TODO: Do state updates when element removed
state
end
end
# Handlers that are used to handle specific events
ACTIONS = {
onElementRemoved: OnElementRemoved,
onElementAdded: OnElementAdded,
onElementModified: OnElementModified
}.freeze
def self.actions
ACTIONS
end
end
end
end
end
@@ -0,0 +1,34 @@
# frozen_string_literal: true
module SpeckleConnector
module Actions
# This module contains actions that are performed to handle events triggered by observers in Sketchup.
module Events
# Base action for Handling events
class EventAction
def self.actions
raise NoMethodError, 'Implement in a subclass'
end
# Handle the events that were collected by the observer. In case of the selection observer,
# we only need to handle the events once if any of the events actually happened.
# @param state [States::State] the current state of the SpeckleConnector Application
# @param events [Hash{Symbol=>Array}] the event data grouped by the event name
# @return [States::State] the transformed state
def self.update_state(state, events)
# Don't do anything if there are no events for this action
return state unless events
actions = self.actions
actions.each do |event_name, action|
next unless events.key?(event_name)
event_data = events[event_name]
state = action.update_state(state, event_data)
end
state
end
end
end
end
end
@@ -0,0 +1,41 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../load_sketchup_model'
module SpeckleConnector
module Actions
module Events
# Handle events that are triggered by the {ModelObserver}.
class ModelEventAction < EventAction
# Handle loading new or existing model
class OnActivePathChanged
# Handle events when the new or existing model is loaded in Sketchup
# @param state [States::State] the current state of speckle application
# @param event_data [Array<(Sketchup::Model)>] the event data for the given event. It consists of
# a double array with a single element that is the {Sketchup::Model} object of the loaded model.
def self.update_state(state, _event_data)
sketchup_state = state.sketchup_state
active_path = sketchup_state.sketchup_model.active_path
observers = state.speckle_state.observers
update_object_observers(active_path, observers)
return state
end
def self.update_object_observers(path, observers)
path[-1].definition.entities.add_observer(observers[ENTITIES_OBSERVER]) unless path.nil?
end
end
# Handlers that are used to handle specific events
ACTIONS = {
onActivePathChanged: OnActivePathChanged
}.freeze
def self.actions
ACTIONS
end
end
end
end
end
@@ -0,0 +1,33 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../../mapping/category/revit_category'
require_relative '../../sketchup_model/reader/speckle_entities_reader'
require_relative '../../sketchup_model/query/entity'
module SpeckleConnector
module Actions
module Events
# Update selected speckle objects when the selection changes for mapping tool.
class SelectionEventAction < EventAction
# @param state [States::State] the current state of Speckle application.
# @return [States::State] the new updated state object
def self.update_state(state, event_data)
return state unless event_data&.any?
sketchup_selection = state.sketchup_state.sketchup_model.selection
selection = {
selection: SketchupModel::Reader::SpeckleEntitiesReader.entities_schema_details(sketchup_selection),
mappingMethods: [
'Direct Shape'
],
categories: Mapping::Category::RevitCategory.to_a
}
selection = { selection: [], mappingMethods: [], categories: [] } if sketchup_selection.none?
state.with_selection_queue(selection)
end
end
end
end
end
@@ -0,0 +1,31 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Hide entities that selected from mapped elements table.
class HideMappingsFromTable < 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)
# Flat entities to clear mappings
flat_entities = SketchupModel::Query::Entity.flat_entities(state.sketchup_state.sketchup_model.entities)
# Collect entity ids to clear mappings
entity_ids = data.collect { |_, entities| entities['selectedElements'].collect { |e| e['entityId'] } }.flatten
# Store speckle state to update with mapped entities.
flat_entities.each do |entity|
next unless entity_ids.include?(entity.persistent_id)
entity.hidden = true
end
Events::SelectionEventAction.update_state(state, { clear: true })
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,21 @@ 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)
preferences = Preferences.read_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)
# This is where we attach observers to related model objects like selection, entities..
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,56 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Isolate entities that selected from mapped elements table.
class IsolateMappingsFromTable < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/CyclomaticComplexity
def self.update_state(state, data)
sketchup_model = state.sketchup_state.sketchup_model
# Hide all entities first
sketchup_model.entities.each do |ent|
ent.hidden = true
end
# Flat entities to isolate mappings
flat_entities = SketchupModel::Query::Entity.flat_entities(sketchup_model.entities)
comp_flat_entities = flat_entities.grep(Sketchup::ComponentInstance) + flat_entities.grep(Sketchup::Group)
face_edge_flat_entities = flat_entities.grep(Sketchup::Face) + flat_entities.grep(Sketchup::Edge)
# Collect entity ids to clear mappings
selected_elements = data.collect { |_, entities| entities['selectedElements'] }.flatten
comps_or_groups, faces_or_edges = selected_elements.partition do |e|
e['entityType'] == 'Component' || e['entityType'] == 'Group'
end
faces_or_edges_ids = faces_or_edges.collect { |e| e['entityId'] }
face_edge_flat_entities.select { |e| faces_or_edges_ids.include?(e.persistent_id) }.each do |entity|
entity.hidden = false
end
comps_or_groups_ids = comps_or_groups.collect { |e| e['entityId'] }
comp_flat_entities.select { |e| comps_or_groups_ids.include?(e.persistent_id) }.each do |entity|
entity.hidden = false
end
Events::SelectionEventAction.update_state(state, { clear: true })
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
end
end
end
@@ -9,7 +9,8 @@ module SpeckleConnector
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
(saved_streams = state.sketchup_state.sketchup_model.attribute_dictionary('speckle', true)['streams']) or []
(saved_streams = state.sketchup_state.sketchup_model
.attribute_dictionary('Speckle', true)['saved_streams']) or []
state.with_add_queue('setSavedStreams', saved_streams, [])
end
end
@@ -0,0 +1,62 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'initialize_materials'
require_relative '../sketchup_model/reader/speckle_entities_reader'
require_relative '../preferences/preferences'
require_relative '../states/state'
require_relative '../states/sketchup_state'
require_relative '../constants/observer_constants'
module SpeckleConnector
module Actions
# Switch sketchup model wit a new one
class LoadSketchupModel < Action
# Replace current model state with the state of a new model. This action is triggered when user opens new or
# existing Sketchup model.
# @param state [States::State] the current state of Speckle
# @param additional_parameters [Array] parameters that the action takes
# @return [States::State] the new updated state object
def self.update_state(state, sketchup_model)
# Init sketchup state again with new model
new_sketchup_state = States::SketchupState.new(sketchup_model)
sketchup_model.rendering_options['DisplaySectionPlanes'] = true
new_state = state.with(:@sketchup_state => new_sketchup_state)
# Init materials again
new_state = InitializeMaterials.update_state(new_state)
# Read speckle entities
new_speckle_entities = SketchupModel::Reader::SpeckleEntitiesReader.read(sketchup_model.entities)
new_speckle_state = new_state.speckle_state.with_speckle_entities(Immutable::Hash.new(new_speckle_entities))
# Read mapped entities
new_mapped_entities = SketchupModel::Reader::SpeckleEntitiesReader.read_mapped_entities(sketchup_model.entities)
new_speckle_state = new_speckle_state.with_mapped_entities(Immutable::Hash.new(new_mapped_entities))
new_state = new_state.with_speckle_state(new_speckle_state)
# Read preferences from database and model.
preferences = Preferences.read_preferences(new_state.sketchup_state.sketchup_model)
new_user_state = new_state.user_state.with_preferences(preferences)
new_state = new_state.with(:@user_state => new_user_state)
attach_observers(sketchup_model, new_state.speckle_state.observers)
new_state
end
# Attach observers to the sketchup model
# @param sketchup_model [Sketchup::Model] the model to attach observers to
# @param observers [Hash{Class=>}] the observer objects indexed by their class that will be attached
def self.attach_observers(sketchup_model, observers)
selection = sketchup_model.selection
selection.add_observer(observers[SELECTION_OBSERVER])
# 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,20 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../sketchup_model/reader/speckle_entities_reader'
module SpeckleConnector
module Actions
# Triggers when mapped entities updated.
class MappedEntitiesUpdated < 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 = nil)
mapped_entities = SketchupModel::Reader::SpeckleEntitiesReader
.mapped_entity_details(state.speckle_state.mapped_entities.values.to_a)
state.with_mapped_entities_queue(mapped_entities)
end
end
end
end
@@ -0,0 +1,35 @@
# 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 'events/selection_event_action'
require_relative '../constants/observer_constants'
module SpeckleConnector
module Actions
# Handle events that were collected by observers
class OnEventsAction < Action
RUN_ORDER = {
APP_OBSERVER => Events::AppEventAction,
ENTITIES_OBSERVER => Events::EntitiesEventAction,
MODEL_OBSERVER => Events::ModelEventAction,
# MATERIALS_OBSERVER => Events::MaterialsEventAction,
# LAYERS_OBSERVER => Events::LayerEventAction,
# PAGES_OBSERVER => Events::PagesEventAction,
SELECTION_OBSERVER => Events::SelectionEventAction
}.freeze
def self.update_state(state, events)
RUN_ORDER.each do |observer_name, action|
next unless events.key?(observer_name)
parameters = events[observer_name]
state = action.update_state(state, parameters)
end
state
end
end
end
end
@@ -8,24 +8,28 @@ module SpeckleConnector
module Actions
# Action to receive objects from Speckle Server.
class ReceiveObjects < Action
def initialize(stream_id, base, stream_name, branch_name, branch_id)
# rubocop:disable Metrics/ParameterLists
def initialize(stream_id, base, stream_name, branch_name, branch_id, source_app)
super()
@stream_id = stream_id
@base = base
@stream_name = stream_name
@branch_name = branch_name
@branch_id = branch_id
@source_app = source_app
end
# rubocop:enable Metrics/ParameterLists
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
converter = Converters::ToNative.new(state.sketchup_state.sketchup_model)
converter = Converters::ToNative.new(state, @stream_id, @stream_name, @branch_name, @source_app)
# Have side effects on the sketchup model. It effects directly on the entities by adding new objects.
start_time = Time.now.to_f
converter.receive_commit_object(@base, state.user_state.preferences[:model])
state = converter.receive_commit_object(@base)
elapsed_time = (Time.now.to_f - start_time).round(3)
puts "==== Converting to Native executed in #{elapsed_time} sec ===="
puts "==== Source application is #{@source_app}. ===="
state.with_add_queue('finishedReceiveInSketchup', @stream_id, [])
end
end
@@ -18,10 +18,10 @@ module SpeckleConnector
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
speckle_dict = state.sketchup_state.sketchup_model.attribute_dictionary('speckle', true)
saved = speckle_dict['streams'] || []
speckle_dict = state.sketchup_state.sketchup_model.attribute_dictionary('Speckle', true)
saved = speckle_dict['saved_streams'] || []
saved -= [@stream_id]
speckle_dict['streams'] = saved
speckle_dict['saved_streams'] = saved
state
end
end
+3 -3
View File
@@ -16,10 +16,10 @@ module SpeckleConnector
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
speckle_dict = state.sketchup_state.sketchup_model.attribute_dictionary('speckle', true)
saved = speckle_dict['streams'] || []
speckle_dict = state.sketchup_state.sketchup_model.attribute_dictionary('Speckle', true)
saved = speckle_dict['saved_streams'] || []
saved = saved.empty? ? [@stream_id] : saved.unshift(@stream_id)
speckle_dict['streams'] = saved
speckle_dict['saved_streams'] = saved
state
end
end
@@ -0,0 +1,34 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Select entities that selected from mapped elements table.
class SelectMappingsFromTable < 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)
# Clear first selection
state.sketchup_state.sketchup_model.selection.clear
# Flat entities to clear mappings
flat_entities = SketchupModel::Query::Entity.flat_entities(state.sketchup_state.sketchup_model.entities)
# Collect entity ids to clear mappings
entity_ids = data.collect { |_, entities| entities['selectedElements'].collect { |e| e['entityId'] } }.flatten
# Store speckle state to update with mapped entities.
flat_entities.each do |entity|
next unless entity_ids.include?(entity.persistent_id)
state.sketchup_state.sketchup_model.selection.add(entity)
end
Events::SelectionEventAction.update_state(state, { clear: true })
end
end
end
end
@@ -16,11 +16,10 @@ module SpeckleConnector
# @return [States::State] the new updated state object
def update_state(state)
to_send_stream_id = state.speckle_state.stream_queue[:stream_id]
return state if to_send_stream_id == @stream_id
return state if to_send_stream_id == @stream_id || to_send_stream_id.nil?
to_send_converted = state.speckle_state.stream_queue[:converted].to_json
new_state = state.with_add_queue('convertedFromSketchup', to_send_stream_id, [to_send_converted])
new_state = new_state.with_add_queue('oneClickSend', to_send_stream_id, [])
new_state.with_empty_stream_queue
end
end
@@ -1,6 +1,7 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'deactivate_diffing'
require_relative '../convertors/units'
require_relative '../convertors/to_speckle'
@@ -16,16 +17,23 @@ module SpeckleConnector
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
sketchup_model = state.sketchup_state.sketchup_model
converter = Converters::ToSpeckle.new(sketchup_model)
base = converter.convert_selection_to_base(state.user_state.preferences)
id, total_children_count, batches = converter.send_info(base)
state = DeactivateDiffing.update_state(state, {})
converter = Converters::ToSpeckle.new(state, @stream_id)
new_speckle_state, base = converter.convert_selection_to_base(state.user_state.preferences)
id, total_children_count, batches, new_speckle_state = converter.serialize(base, new_speckle_state,
state.user_state.preferences)
puts("converted #{base.count} objects for stream #{@stream_id}")
state.with_add_queue('convertedFromSketchup', @stream_id, [
{ is_string: false, val: batches },
{ is_string: true, val: id },
{ is_string: false, val: total_children_count }
])
# This is the place we can send information to UI for diffing check
diffing = state.user_state.preferences[:user][:diffing]
new_speckle_state = new_speckle_state.with_invalid_streams_queue if diffing
new_state = state.with_speckle_state(new_speckle_state)
new_state.with_add_queue('convertedFromSketchup', @stream_id, [
{ is_string: false, val: batches },
{ is_string: true, val: id },
{ is_string: false, val: total_children_count }
])
end
end
end
@@ -0,0 +1,20 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Show all entities on the model.
class ShowAllEntities < 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)
# Show all entities first
state.sketchup_state.sketchup_model.entities.each do |ent|
ent.hidden = false
end
state
end
end
end
end
@@ -18,6 +18,8 @@ module SpeckleConnector
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
def update_state(state)
# Init sqlite database
db = Sqlite3::Database.new(SPECKLE_CONFIG_DB_PATH)
@@ -46,8 +48,19 @@ module SpeckleConnector
user[@preference.to_sym] = @value
new_preferences = state.user_state.preferences.put(:user, user)
new_user_state = state.user_state.with_preferences(new_preferences)
# This is the place we can send information to UI for diffing check. It is a technical depth!
if @preference == 'diffing'
new_speckle_state = if @value
state.speckle_state.with_invalid_streams_queue
else
state.speckle_state.with_empty_invalid_streams_queue
end
state = state.with_speckle_state(new_speckle_state)
end
state.with_user_state(new_user_state)
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
end
end
end
@@ -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
@@ -29,12 +32,20 @@ module SpeckleConnector
ui_controller.update_ui(state)
end
# Attach observers to application when speckle initialized via menu commands.
def add_observer_handler!(observer_handler)
@observer_handler = observer_handler
end
# Send messages to HtmlDialog if any.
def send_messages!
queue = @state.speckle_state.message_queue
queue.each_value { |value| ui_controller.user_interfaces[Ui::SPECKLE_UI_ID].dialog.execute_script(value) }
update_state!(Actions::ClearQueue)
end
# This is the only function application state will be switched by calling upcoming action with it's parameters
# if any.
def update_state!(action, *parameters)
old_state = @state
@state = action.update_state(old_state, *parameters)
@@ -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
@@ -0,0 +1,21 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/apply_mappings'
module SpeckleConnector
module Commands
# Command to apply mapping for selected entities.
class ApplyMappings < Command
def _run(data)
entities_to_map = data['entitiesToMap']
method = data['method']
category = data['category']
name = data['name']
is_definition = data['isDefinition']
action = Actions::ApplyMappings.new(entities_to_map, method, category, name, is_definition)
app.update_state!(action)
end
end
end
end
@@ -0,0 +1,18 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/clear_mappings'
module SpeckleConnector
module Commands
# Command to clear mapping for selected entities.
class ClearMappings < Command
def _run(data)
entities_to_map = data['entitiesToClearMap']
is_definition = data['isDefinition']
action = Actions::ClearMappings.new(entities_to_map, is_definition)
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,
@@ -13,7 +13,8 @@ module SpeckleConnector
branch_name = data['branch_name']
branch_id = data['branch_id']
stream_name = data['stream_name']
action = Actions::ReceiveObjects.new(stream_id, base, stream_name, branch_name, branch_id)
source_app = data['source_app']
action = Actions::ReceiveObjects.new(stream_id, base, stream_name, branch_name, branch_id, source_app)
app.update_state!(action)
end
end
@@ -1,8 +1,16 @@
# frozen_string_literal: true
module SpeckleConnector
SPECKLE_BASE_OBJECT = 'Speckle_Base_Object'
SPECKLE_MAPPING_TOOL_SCHEMA = 'Speckle_Mapping_Tool_Schema'
SPECKLE_SCHEMA = 'Speckle_Schema'
SPECKLE_ID = 'speckle_id'
SPECKLE_TYPE = 'speckle_type'
APPLICATION_ID = 'application_id'
TOTAL_CHILDREN_COUNT = 'total_children_count'
CHILDREN = 'children'
PARENT = 'parent'
VALID_STREAM_IDS = 'valid_stream_ids'
INVALID_STREAM_IDS = 'invalid_stream_ids'
end
@@ -0,0 +1,22 @@
# frozen_string_literal: true
module SpeckleConnector
MAT_DICTIONARY = 'Speckle_Connector_Materials'
MAT_ID = 'Speckle_Connector_Material_Id'
MAT_ADD = :speckle_connector_add_material
MAT_EDIT = :speckle_connector_edit_material
MAT_REMOVE = :speckle_connector_remove_material
DEFAULT_COLORS = {
MAT_ADD => '#66FF66',
MAT_EDIT => '#FFFF9F',
MAT_REMOVE => '#FF6666'
}.freeze
DEFAULT_NAMES = {
MAT_ADD => 'Speckle_Material_Add',
MAT_EDIT => 'Speckle_Material_Edit',
MAT_REMOVE => 'Speckle_Material_Remove'
}.freeze
end
@@ -0,0 +1,8 @@
# frozen_string_literal: true
module SpeckleConnector
APP_OBSERVER = 'SpeckleConnector::Observers::AppObserver'
ENTITIES_OBSERVER = 'SpeckleConnector::Observers::EntitiesObserver'
MODEL_OBSERVER = 'SpeckleConnector::Observers::ModelObserver'
SELECTION_OBSERVER = 'SpeckleConnector::Observers::SelectionObserver'
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
@@ -0,0 +1,32 @@
# frozen_string_literal: true
require_relative '../speckle_objects/geometry/length'
module SpeckleConnector
COMBINE_FACES_BY_MATERIAL = :combine_faces_by_material
INCLUDE_ENTITY_ATTRIBUTES = :include_entity_attributes
INCLUDE_FACE_ENTITY_ATTRIBUTES = :include_face_entity_attributes
INCLUDE_EDGE_ENTITY_ATTRIBUTES = :include_edge_entity_attributes
INCLUDE_GROUP_ENTITY_ATTRIBUTES = :include_group_entity_attributes
INCLUDE_COMPONENT_ENTITY_ATTRIBUTES = :include_component_entity_attributes
MERGE_COPLANAR_FACES = :merge_coplanar_faces
ENTITY_KEYS_FOR_INCLUDING_ATTRIBUTES = {
Sketchup::ComponentInstance => INCLUDE_COMPONENT_ENTITY_ATTRIBUTES,
Sketchup::Group => INCLUDE_GROUP_ENTITY_ATTRIBUTES,
Sketchup::Face => INCLUDE_FACE_ENTITY_ATTRIBUTES,
Sketchup::Face => INCLUDE_EDGE_ENTITY_ATTRIBUTES
}.freeze
LEVEL_SHIFT_VALUE = SpeckleObjects::Geometry.length_to_native(1.5, 'm')
DEFAULT_MODEL_PREFERENCES = {
COMBINE_FACES_BY_MATERIAL => true,
INCLUDE_ENTITY_ATTRIBUTES => true,
INCLUDE_FACE_ENTITY_ATTRIBUTES => true,
INCLUDE_EDGE_ENTITY_ATTRIBUTES => true,
INCLUDE_GROUP_ENTITY_ATTRIBUTES => true,
INCLUDE_COMPONENT_ENTITY_ATTRIBUTES => true,
MERGE_COPLANAR_FACES => true
}.freeze
end
@@ -2,4 +2,22 @@
module SpeckleConnector
BASE_OBJECT = 'Base'
OBJECTS_GIS_POLYGONELEMENT = 'Objects.GIS.PolygonElement'
OBJECTS_BUILTELEMENTS_VIEW3D = 'Objects.BuiltElements.View:Objects.BuiltElements.View3D'
OBJECTS_BUILTELEMENTS_REVIT_DIRECTSHAPE = 'Objects.BuiltElements.Revit.DirectShape'
OBJECTS_BUILTELEMENTS_REVIT_LEVEL = 'Objects.BuiltElements.Level:Objects.BuiltElements.Revit.RevitLevel'
OBJECTS_GEOMETRY_LINE = 'Objects.Geometry.Line'
OBJECTS_GEOMETRY_POLYLINE = 'Objects.Geometry.Polyline'
OBJECTS_GEOMETRY_MESH = 'Objects.Geometry.Mesh'
OBJECTS_GEOMETRY_BREP = 'Objects.Geometry.Brep'
OBJECTS_OTHER_BLOCKINSTANCE = 'Objects.Other.BlockInstance'
OBJECTS_OTHER_BLOCKINSTANCE_FULL = 'Objects.Other.Instance:Objects.Other.BlockInstance'
OBJECTS_OTHER_INSTANCE = 'Objects.Other.Instance:Objects.Other.Instance'
OBJECTS_OTHER_REVIT_REVITINSTANCE = 'Objects.Other.Revit.RevitInstance'
OBJECTS_OTHER_BLOCKDEFINITION = 'Objects.Other.BlockDefinition'
OBJECTS_OTHER_RENDERMATERIAL = 'Objects.Other.RenderMaterial'
end
@@ -5,6 +5,7 @@ require 'securerandom'
# rubocop:enable SketchupPerformance/OpenSSL
require 'digest'
require_relative 'converter'
require_relative '../speckle_entities/speckle_entity'
require_relative '../relations/many_to_one_relation'
module SpeckleConnector
@@ -15,7 +16,16 @@ module SpeckleConnector
# @return [Integer] default chunk size the determine splitting base prop into chucks
attr_reader :default_chunk_size
def initialize(default_chunk_size = 1000)
# @return [String] stream id to send conversion
attr_reader :stream_id
attr_accessor :speckle_state
# @param stream_id [String] stream id to send conversion
def initialize(speckle_state, stream_id, preferences, default_chunk_size = 1000)
@speckle_state = speckle_state
@stream_id = stream_id
@preferences = preferences
@default_chunk_size = default_chunk_size
@detach_lineage = []
@lineage = []
@@ -30,31 +40,55 @@ module SpeckleConnector
def serialize(base)
id, traversed = traverse_base(base)
@objects[id] = traversed
return id, traversed
id
end
def total_children_count(id)
@objects[id][:totalChildrenCount]
end
# @param base [Object] base object to populate all children and their relationship
# @param base_and_entities [Object] base object to populate all children and their relationship
# rubocop:disable Metrics/MethodLength
def traverse_base(base)
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/AbcSize
def traverse_base(base_and_entities)
base, entities = base_and_entities
# 1. Create random string for lineage tracking.
@lineage.append(SecureRandom.hex)
# 2. Initialize traversed base object that will be filled with traversed values or
# 2. Get last item from detach_lineage array
is_detached = @detach_lineage.pop
# unless entities.nil?
# is_sent_before = entities.all? do |entity|
# check_base_available_on_state(entity, speckle_state)
# end
# if is_sent_before
# speckle_entity = speckle_state.speckle_entities[entities.first.persistent_id]
# ref_object = detach_helper(speckle_entity.id)
# parent = @lineage[-1]
# unless @family_tree[parent].nil?
# @family_tree[parent] = @family_tree[parent].merge(speckle_entity.speckle_object[:__closure])
# end
# @objects[speckle_entity.id] = ref_object if is_detached
# return speckle_entity.id, ref_object
# end
# end
# 3. Initialize traversed base object that will be filled with traversed values or
# traversed base objects as props.
traversed_base = SpeckleObjects::Base.new(speckle_type: base[:speckle_type], id: '')
# 3.1 Remove applicationId if it is nil
traversed_base.delete(:applicationId)
# 3. Iterate all entries (key, value) of the base {Base > Hash} object
# 4. Iterate all entries (key, value) of the base {Base > Hash} object
# speckle_state = traverse_base_props(base, traversed_base)
traverse_base_props(base, traversed_base)
# this is where all props are done for current `traversed_base`
# 4. Get last item from detach_lineage array
is_detached = @detach_lineage.pop
# 5. Add closures
closure = {}
parent = @lineage.pop
@@ -82,9 +116,19 @@ module SpeckleConnector
# 10. Save object string if detached
@objects[id] = traversed_base if is_detached
if @preferences[:user][:register_speckle_entity] && !entities.nil?
entities.uniq.each do |entity|
speckle_entity = create_or_update_speckle_entity(entity, id, traversed_base)
@speckle_state = speckle_state.with_speckle_entity(speckle_entity)
end
end
return id, traversed_base
end
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
@@ -102,14 +146,16 @@ module SpeckleConnector
next
end
# 3.3. Determine prop is detached or not
is_prop_detach = prop[0] == '@'
# 3.3. Determine prop is dynamically detached or not
is_detach_prop = prop[0] == '@'
is_dynamically_detached = prop[0] == '@' && prop.length > 2 && prop[1] == '@'
prop = prop[2..-1] if is_dynamically_detached
# 3.4. Check prop needs to split into chunks
chunked_detach_match = prop.match(/^@\((\d*)\)/)
# 3.5. If split chunk is needed and prop value is array, then run chunking process
if value.is_a?(Array) && chunked_detach_match
if value.is_a?(Array) && !base_and_entities?(value) && chunked_detach_match
# 3.5.1. Determine chunk size, get it from prop if defined. ex: '@(31250)faces' -> 31250 = chunk size
chunk_size = chunked_detach_match[1] == '' ? default_chunk_size : chunked_detach_match[1].to_i
@@ -144,7 +190,7 @@ module SpeckleConnector
chunk_references = []
chunks.each do |chunk_element|
@detach_lineage.append(is_prop_detach)
@detach_lineage.append(is_detach_prop)
id, _traversed = traverse_base(chunk_element)
chunk_references.append(detach_helper(id))
end
@@ -152,17 +198,21 @@ module SpeckleConnector
# 3.5.7. Add chunk references to the traversed base prop without @(<chunk_size>)
traversed_base[prop.to_s.sub(chunked_detach_match[0], '')] = chunk_references
# 3.5.8. We are done chunking, good to go next
# 3.5.8. We are done chunking, good to go next property
next
end
child = traverse_value(value, is_detach_prop)
is_base = (value.is_a?(Hash) && !value[:speckle_type].nil?) ||
(base_and_entities?(value) && value[0].is_a?(Hash) && !value[0][:speckle_type].nil?)
# 3.6. traverse value according to value is a speckle object or not
if value.is_a?(Hash) && !value[:speckle_type].nil?
child = traverse_value(value, is_prop_detach)
traversed_base[prop] = is_prop_detach ? detach_helper(child[:id]) : child
else
traversed_base[prop] = traverse_value(value, is_prop_detach)
end
traversed_base[prop] = if is_base
is_detach_prop ? detach_helper(child[:id]) : child
else
child
end
end
end
# rubocop:enable Metrics/MethodLength
@@ -171,38 +221,59 @@ 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
return false if value[1].nil?
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 +286,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 +335,32 @@ module SpeckleConnector
batches
end
# rubocop:enable Metrics/MethodLength
# @param entity [Sketchup::Entity] source entity object
# @param speckle_state [States::SpeckleState] the current speckle state of the {States::State}
def check_base_available_on_state(entity, speckle_state)
is_exist = speckle_state.speckle_entities.keys.include?(entity.persistent_id)
return is_exist unless is_exist
speckle_state.speckle_entities[entity.persistent_id].valid_stream_ids.include?(stream_id)
end
# Creates or updates speckle entity.
# If speckle entity exist in state, creates new one by updating old one.
# Else creates new one
# @return [SpeckleEntity] speckle entity that collects both speckle and sketchup information.
def create_or_update_speckle_entity(entity, id, traversed_base)
if speckle_state.speckle_entities.keys.include?(entity.persistent_id)
speckle_state.speckle_entities[entity.persistent_id].with_valid_stream_id(stream_id)
else
children = traversed_base[:__closure].nil? ? {} : traversed_base[:__closure]
speckle_entity = SpeckleEntities::SpeckleEntity.new(entity, id, entity.persistent_id,
traversed_base[:speckle_type],
children.keys, [stream_id])
speckle_entity.write_initial_base_data
speckle_entity
end
end
end
end
end
@@ -17,6 +17,11 @@ module SpeckleConnector
faces.each { |face| face.edges.each { |edge| edges << edge } }
edges.uniq!
edges.each { |edge| remove_edge_have_coplanar_faces(edge, faces, false) }
merged_faces(faces)
end
def self.merged_faces(faces)
faces.reject(&:deleted?)
end
# Detect edges to remove by checking following controls respectively;
+20 -6
View File
@@ -4,18 +4,32 @@ module SpeckleConnector
module Converters
# Helper class to convert geometries between server and Sketchup.
class Converter
# @return [States::State] the current state of the {SpeckleConnector::App}
attr_reader :state
# @return [States::SpeckleState] the current speckle state of the {States::State}
attr_reader :speckle_state
# @return [Sketchup::Model] active sketchup model.
attr_reader :sketchup_model
attr_accessor :units, :definitions, :registry, :entity_observer
# @return [String] stream id that conversion happening with it
attr_reader :stream_id
def initialize(sketchup_model)
@sketchup_model = sketchup_model
su_unit = @sketchup_model.options['UnitsOptions']['LengthUnit']
# @return [String] speckle units
attr_reader :units
attr_accessor :definitions
# @param state [States::State] the current state of the {SpeckleConnector::App}
def initialize(state, stream_id)
@state = state
@speckle_state = state.speckle_state
@sketchup_model = state.sketchup_state.sketchup_model
@stream_id = stream_id
su_unit = state.sketchup_state.length_units
@units = Converters::SKETCHUP_UNITS[su_unit]
@definitions = {}
# @registry = Sketchup.active_model.attribute_dictionary("speckle_id_registry", true)
# @entity_observer = SpeckleEntityObserver.new
end
end
end
+281 -158
View File
@@ -1,21 +1,44 @@
# frozen_string_literal: true
require_relative 'converter'
require_relative '../constants/type_constants'
require_relative '../speckle_objects/gis/polygon_element'
require_relative '../speckle_objects/other/transform'
require_relative '../speckle_objects/other/render_material'
require_relative '../speckle_objects/other/block_definition'
require_relative '../speckle_objects/other/block_instance'
require_relative '../speckle_objects/other/display_value'
require_relative '../speckle_objects/revit/revit_instance'
require_relative '../speckle_objects/geometry/point'
require_relative '../speckle_objects/geometry/line'
require_relative '../speckle_objects/geometry/mesh'
require_relative '../speckle_objects/built_elements/view3d'
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
module SpeckleConnector
module Converters
# Converts sketchup entities to speckle objects.
# rubocop:disable Metrics/ClassLength
class ToNative < Converter
# @return [States::SpeckleState] the current speckle state of the {States::State}
attr_accessor :speckle_state
# @return [String] source application of received object that will be converted to native
attr_reader :source_app
def initialize(state, stream_id, stream_name, branch_name, source_app)
super(state, stream_id)
@stream_name = stream_name
@branch_name = branch_name
@source_app = source_app.downcase
end
# Module aliases
GEOMETRY = SpeckleObjects::Geometry
OTHER = SpeckleObjects::Other
REVIT = SpeckleObjects::Revit
BUILTELEMENTS = SpeckleObjects::BuiltElements
GIS = SpeckleObjects::GIS
# Class aliases
POINT = GEOMETRY::Point
@@ -23,6 +46,11 @@ module SpeckleConnector
MESH = GEOMETRY::Mesh
BLOCK_DEFINITION = OTHER::BlockDefinition
BLOCK_INSTANCE = OTHER::BlockInstance
REVIT_INSTANCE = REVIT::Other::RevitInstance
RENDER_MATERIAL = OTHER::RenderMaterial
DISPLAY_VALUE = OTHER::DisplayValue
VIEW3D = BUILTELEMENTS::View3d
POLYGON_ELEMENT = GIS::PolygonElement
BASE_OBJECT_PROPS = %w[applicationId id speckle_type totalChildrenCount].freeze
CONVERTABLE_SPECKLE_TYPES = %w[
@@ -31,11 +59,163 @@ module SpeckleConnector
Objects.Geometry.Mesh
Objects.Geometry.Brep
Objects.Other.BlockInstance
Objects.Other.Revit.RevitInstance
Objects.Other.BlockDefinition
Objects.Other.RenderMaterial
Objects.Other.Instance:Objects.Other.BlockInstance
Objects.BuiltElements.View:Objects.BuiltElements.View3D
Objects.GIS.PolygonElement
].freeze
def can_convert_to_native(obj)
def from_revit
@from_revit ||= source_app.include?('revit')
end
def from_sketchup
@from_sketchup ||= source_app.include?('sketchup')
end
def from_qgis
@from_qgis ||= source_app.include?('qgis')
end
# ReceiveObjects action call this method by giving everything that comes from server.
# Upcoming object is a referencedObject of selected commit to receive.
# UI is responsible currently to fetch objects from ObjectLoader module by calling getAndConstruct method.
# @param obj [Object] speckle commit object.
def receive_commit_object(obj)
# First create layers on the sketchup before starting traversing
# @Named Views are exception here. It does not mean a layer. But it is anti-pattern for now.
layers_relation = obj['layers_relation']
# Create layers and it's folders from layers relation on the model collection.
SpeckleObjects::Relations::Layers.to_native(layers_relation, sketchup_model) if layers_relation && !from_revit
# By default entities to fill is sketchup model's entities.
@entities_to_fill = sketchup_model.entities
# Navigate to branch entities if commit doesn't come from sketchup
unless from_sketchup
@branch_definition = branch_definition
@entities_to_fill = @branch_definition.entities
end
traverse_commit_object(obj, @entities_to_fill)
create_levels_from_section_planes
check_hiding_layers_needed
try_create_instance
@state
end
# Creating instance from @branch_definition only available for non-sketchup commits since we wrap commits
# under instance.
# There is also another use case that maybe definition is exist in file but user might be deleted it before.
# If this is the case we can add instance by checking number of instances.
# rubocop:disable Style/GuardClause
def try_create_instance
if !from_sketchup && (!@is_update_commit || @branch_definition.instances.empty?)
instance = sketchup_model.entities.add_instance(@branch_definition, Geom::Transformation.new)
BLOCK_INSTANCE.align_instance_axes(instance) if from_qgis
end
end
# rubocop:enable Style/GuardClause
def levels_layer
@levels_layer ||= sketchup_model.layers.add('Levels')
end
def clear_levels
instances = @entities_to_fill.grep(Sketchup::ComponentInstance)
instances.each do |instance|
speckle_type = instance.get_attribute(SPECKLE_BASE_OBJECT, 'speckle_type')
next if speckle_type.nil?
sketchup_model.definitions.remove(instance.definition) if speckle_type == OBJECTS_BUILTELEMENTS_REVIT_LEVEL
end
end
# Create levels from section planes that already created for this commit object.
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
def create_levels_from_section_planes
clear_levels if @is_update_commit
return unless from_revit
section_planes = @entities_to_fill.grep(Sketchup::SectionPlane)
bbox = @entities_to_fill.parent.bounds
c_1 = bbox.corner(0)
c_2 = bbox.corner(1)
c_3 = bbox.corner(3)
c_4 = bbox.corner(2)
section_planes.each do |section_plane|
level_name = "#{@definition_name}-#{section_plane.name}"
definition = sketchup_model.definitions.add(level_name)
instance = @entities_to_fill.add_instance(definition, Geom::Transformation.new)
att = section_plane.attribute_dictionary(SPECKLE_BASE_OBJECT).to_h
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.set_hash(instance, att)
elevation = section_plane.bounds.center.z
c1_e = Geom::Point3d.new(c_1.x, c_1.y, elevation - LEVEL_SHIFT_VALUE)
c2_e = Geom::Point3d.new(c_2.x, c_2.y, elevation - LEVEL_SHIFT_VALUE)
c3_e = Geom::Point3d.new(c_3.x, c_3.y, elevation - LEVEL_SHIFT_VALUE)
c4_e = Geom::Point3d.new(c_4.x, c_4.y, elevation - LEVEL_SHIFT_VALUE)
cline_1 = definition.entities.add_cline(c1_e, c2_e)
cline_2 = definition.entities.add_cline(c2_e, c3_e)
cline_3 = definition.entities.add_cline(c3_e, c4_e)
cline_4 = definition.entities.add_cline(c4_e, c1_e)
text = definition.entities.add_text(" #{section_plane.name}", c1_e)
[cline_1, cline_2, cline_3, cline_4, text, definition].each { |o| o.layer = levels_layer }
end
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# @return [Sketchup::ComponentDefinition] branch definition to fill objects in it.
def branch_definition
@definition_name = "#{@branch_name}-#{@stream_name}"
definition = sketchup_model.definitions.find { |d| d.name == @definition_name }
@is_update_commit = !definition.nil?
definition = sketchup_model.definitions.add(@definition_name) if definition.nil?
definition
end
def entities_to_fill(_obj)
return sketchup_model.entities unless from_revit
@definition_name = "#{@branch_name}-#{@stream_name}"
definition = sketchup_model.definitions.find { |d| d.name == @definition_name }
if definition.nil?
definition = sketchup_model.definitions.add(@definition_name)
sketchup_model.entities.add_instance(definition, Geom::Transformation.new)
end
definition
end
LAYERS_WILL_BE_HIDDEN = [
'Rooms',
'Mass',
'Mass Floor',
'Grid',
'Shaft Openings'
].freeze
def check_hiding_layers_needed
return unless from_revit
sketchup_model.layers.each do |layer|
if LAYERS_WILL_BE_HIDDEN.any? { |layer_name| layer.display_name.include?(layer_name) }
layer.visible = false
sketchup_model.pages.each { |page| page.update(PAGE_USE_LAYER_VISIBILITY) }
end
end
end
# Conditions for converting speckle object to native sketchup entity:
# 1- `obj` is a hash
# 2- `obj` has a property as 'speckle_type'
# 3- `obj` is a convertable 'speckle_type' which sketchup supports
# @param obj [Object] candidate object to convert from speckle to sketchup.
# @return [Boolean] whether object is convertable or not.
def convertible_to_native?(obj)
return false unless obj.is_a?(Hash) && obj.key?('speckle_type')
CONVERTABLE_SPECKLE_TYPES.include?(obj['speckle_type'])
@@ -45,71 +225,14 @@ module SpeckleConnector
['Objects.BuiltElements.Revit.Parameter'].include?(obj['speckle_type'])
end
# @param obj [Object] speckle commit object.
def receive_commit_object(obj, model_preferences)
# First create layers on the sketchup before starting traversing
create_layers(obj.keys.filter_map { |key| key if key.start_with?('@') }, sketchup_model.layers)
# Define default commit layer which is the fallback
default_commit_layer = sketchup_model.layers.layers.find { |layer| layer.display_name == '@Untagged' }
traverse_commit_object(obj, sketchup_model.layers, default_commit_layer, model_preferences)
end
# Create actual Sketchup layers from layer_paths that taken from Speckle base object.
# @param layer_paths [Array<String>] layer paths to decompose it to folders and it's layers.
# @param folder [Sketchup::Layers, Sketchup::LayerFolder] folder to create folders and layers under it.
def create_layers(layer_paths, folder)
# Strip leading '@'
layers_with_folders = layer_paths.map { |layer| layer[1..-1] }
# Split layer_paths according to having parent folder or not.
layers_with_head_folder, headless_layers = layers_with_folders.partition { |layer| layer.include?('::') }
# Create array of array that split with '::'
folder_layer_arrays = layers_with_head_folder.collect { |folder_layer| folder_layer.split('::') }
# Add headless layers into `Sketchup.active_model.layers`
create_headless_layers(headless_layers, folder)
# Create layers that have parent folder(s)- this method is recursive until all tree is created.
create_folder_layers(folder_layer_arrays, folder)
end
# @param headless_layers [Array<String>] headless layer names.
# @param folder [Sketchup::Layers, Sketchup::LayerFolder] layer folder to create commit layers under it.
def create_headless_layers(headless_layers, folder)
headless_layers.each do |layer_name|
# Add layer first to the layers object of sketchup model.
layer = sketchup_model.layers.add(layer_name)
folder.add_layer(layer) unless folder.layers.any? { |l| l.display_name == layer_name }
end
end
# Create layers with it's parent folders.
# @param folder [Sketchup::LayerFolder] layer folder to create commit layers under it.
def create_folder_layers(folder_layer_arrays, folder)
folder_layer_arrays.each do |folder_layer_array|
create_folder_layer(folder_layer_array, folder)
end
end
# Create layers that have parent folder(s)- this method is recursive (self-caller) until all tree is created.
def create_folder_layer(folder_array, folder)
if folder_array.length > 1
# add folder if it is not exist.
folder.add_folder(folder_array[0]) unless folder.folders.any? { |f| f.display_name == folder_array[0] }
new_folder = folder.folders.find { |f| f.display_name == folder_array[0] }
create_folder_layer(folder_array[1..-1], new_folder)
else
# Add layer first to the layers object of sketchup model.
layer = sketchup_model.layers.add(folder_array[0])
folder.add_layer(layer) unless folder.layers.any? { |l| l.display_name == layer }
end
end
# Traversal method to create Sketchup objects from upcoming base object.
# @param obj [Hash, Array] object might be source base object or it's sub objects, because this method is a
# self-caller method means that call itself according to conditions inside of it.
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def traverse_commit_object(obj, commit_folder, layer, model_preferences)
if can_convert_to_native(obj)
convert_to_native(obj, layer, model_preferences)
def traverse_commit_object(obj, entities)
if convertible_to_native?(obj)
@state = convert_to_native(@state, obj, entities)
elsif obj.is_a?(Hash) && obj.key?('speckle_type')
return if ignored_speckle_type?(obj)
@@ -117,131 +240,131 @@ module SpeckleConnector
# puts(">>> Found #{obj['speckle_type']}: #{obj['id']}. Continuing traversal.")
props = obj.keys.filter_map { |key| key unless key.start_with?('_') }
props.each do |prop|
layer_path = prop if prop.start_with?('@') && obj[prop].is_a?(Array)
layer = find_layer(layer_path, commit_folder, layer)
traverse_commit_object(obj[prop], commit_folder, layer, model_preferences)
traverse_commit_object(obj[prop], entities)
end
else
# puts(">>> Found #{obj['speckle_type']}: #{obj['id']} with displayValue.")
convert_to_native(obj, layer, model_preferences)
@state = convert_to_native(@state, obj, entities)
end
elsif obj.is_a?(Hash)
obj.each_value { |value| traverse_commit_object(value, commit_folder, layer, model_preferences) }
obj.each_value { |value| traverse_commit_object(value, entities) }
elsif obj.is_a?(Array)
obj.each { |value| traverse_commit_object(value, commit_folder, layer, model_preferences) }
obj.each { |value| traverse_commit_object(value, entities) }
end
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# Find layer of the Speckle object by checking iteratively into folder.
# @param layer_path [String] complete layer_path to retrieve
# @param folder [Sketchup::LayerFolder, Sketchup::Layers] entry folder to search layer
# @param fallback_layer [Sketchup::Layer] fallback layer to assign object later if any error occur.
# @return [Sketchup::Layer] layer according to path
# @example
# "@folder_1::folder_2::layer_1"
# # it will return the layer object which has display name as `layer_1`.
def find_layer(layer_path, folder, fallback_layer)
begin
# Split folders and it's tail layer (last one is layer, others are folders.)
layer_path_array = layer_path[1..-1].split('::')
# Get sub folders as array, might be empty if `layer_path_array` has only 1 entry
sub_folders = layer_path_array.length > 1 ? layer_path_array[0..-2] : []
# Get exact layer name from last entry
layer_name = layer_path_array.last
# Iterate sub folders to find new sub folder to switch it.
# It help to search in the tree by switching the target search folder.
# Finally we can reach the layer name.
sub_folders.each do |sub_folder|
# Try to find sub folder into source folder passes by argument
s_f = folder.folders.find { |f| f.display_name == sub_folder }
# Switch source folder if any exist
folder = s_f unless s_f.nil?
end
# Find finally the layer into related folder
folder.layers.find { |l| l.display_name == layer_name }
rescue StandardError
return fallback_layer
end
def speckle_object_to_native(obj)
return DISPLAY_VALUE.method(:to_native) unless obj['displayValue'].nil?
SPECKLE_OBJECT_TO_NATIVE[obj['speckle_type']]
end
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/MethodLength
def convert_to_native(obj, layer, model_preferences, entities = sketchup_model.entities)
convert = method(:convert_to_native)
unless obj['displayValue'].nil?
return display_value_to_native_component(obj, layer, entities, model_preferences, &convert)
end
SPECKLE_OBJECT_TO_NATIVE = {
OBJECTS_GEOMETRY_LINE => LINE.method(:to_native),
OBJECTS_GEOMETRY_POLYLINE => LINE.method(:to_native),
OBJECTS_GEOMETRY_MESH => MESH.method(:to_native),
OBJECTS_GEOMETRY_BREP => MESH.method(:to_native),
OBJECTS_OTHER_BLOCKDEFINITION => BLOCK_DEFINITION.method(:to_native),
OBJECTS_OTHER_BLOCKINSTANCE => BLOCK_INSTANCE.method(:to_native),
OBJECTS_OTHER_BLOCKINSTANCE_FULL => BLOCK_INSTANCE.method(:to_native),
OBJECTS_OTHER_REVIT_REVITINSTANCE => REVIT_INSTANCE.method(:to_native),
OBJECTS_OTHER_RENDERMATERIAL => RENDER_MATERIAL.method(:to_native),
OBJECTS_BUILTELEMENTS_VIEW3D => VIEW3D.method(:to_native),
OBJECTS_GIS_POLYGONELEMENT => POLYGON_ELEMENT.method(:to_native)
}.freeze
case obj['speckle_type']
when 'Objects.Geometry.Line', 'Objects.Geometry.Polyline' then LINE.to_native(obj, layer, entities)
when 'Objects.Other.BlockInstance' then BLOCK_INSTANCE.to_native(sketchup_model, obj, layer, entities,
model_preferences, &convert)
when 'Objects.Other.BlockDefinition' then BLOCK_DEFINITION.to_native(sketchup_model, obj, layer,
obj['name'],
obj['always_face_camera'],
model_preferences,
obj['sketchup_attributes'],
obj['applicationId'],
&convert)
when 'Objects.Geometry.Mesh' then MESH.to_native(sketchup_model, obj, layer, entities, model_preferences)
when 'Objects.Geometry.Brep' then MESH.to_native(sketchup_model, obj['displayValue'], layer, entities,
model_preferences)
# @param state [States::State] state of the speckle application
def convert_to_native(state, obj, entities = sketchup_model.entities)
# store this method as parameter to re-call it inner callstack
convert_to_native = method(:convert_to_native)
# Get 'to_native' method to convert upcoming speckle object to native sketchup entity
to_native_method = speckle_object_to_native(obj)
# Call 'to_native' method by passing this method itself to handle nested 'to_native' conversions.
# It returns updated state and converted entities.
state, converted_entities = to_native_method.call(state, obj, entities, &convert_to_native)
if from_revit
# Create levels as section planes if they exists
create_levels(state, obj)
# Create layers from category of object and place object in it
create_layers_from_categories(state, obj, converted_entities)
end
# Create speckle entities from sketchup entities to achieve continuous traversal.
convert_to_speckle_entities(state, obj, converted_entities)
rescue StandardError => e
puts("Failed to convert #{obj['speckle_type']} (id: #{obj['id']})")
puts(e)
nil
return state
end
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def create_layers_from_categories(state, speckle_object, entities)
return state if speckle_object['category'].nil?
layer = sketchup_model.layers.find { |l| l.display_name == speckle_object['category'] }
unless layer.nil?
entities.each { |entity| entity.layer = layer if entity.respond_to?(:layer) } if layer
return state
end
layer = sketchup_model.layers.add(speckle_object['category'])
unless layer.nil?
entities.each { |entity| entity.layer = layer if entity.respond_to?(:layer) } if layer
state
end
state
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/PerceivedComplexity
# Creates a component definition and instance from a speckle object with a display value
# @param state [States::State] state of the speckle application
def create_levels(state, speckle_object)
level = speckle_object['level']
return state if level.nil?
return state unless level['speckle_type'].include?('Objects.BuiltElements.Level')
level_name = level['name'] || level['id']
is_exist = @entities_to_fill.grep(Sketchup::SectionPlane).any? { |sp| sp.name == level_name }
return state if is_exist
elevation = SpeckleObjects::Geometry.length_to_native(level['elevation'], level['units'])
section_plane = @entities_to_fill.add_section_plane([0, 0, elevation + LEVEL_SHIFT_VALUE], [0, 0, -1])
section_plane.name = level_name
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.write_initial_base_data(
section_plane, level['applicationId'], level['id'], level['speckle_type'], [], @stream_id
)
state
end
# @param state [States::State] state of the application
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/MethodLength
def display_value_to_native_component(obj, layer, entities, model_preferences, &convert)
obj_id = obj['applicationId'].to_s.empty? ? obj['id'] : obj['applicationId']
def convert_to_speckle_entities(state, speckle_object, entities)
return state if entities.empty?
block_definition = obj['@blockDefinition'] || obj['blockDefinition']
speckle_id = speckle_object['id']
application_id = speckle_object['applicationId']
speckle_type = speckle_object['speckle_type']
children = speckle_object['__closure'].nil? ? [] : speckle_object['__closure']
speckle_state = state.speckle_state
entities.each do |entity|
next if entity.is_a?(Sketchup::Material) || entity.is_a?(Sketchup::Page)
next if (entity.is_a?(Sketchup::Face) || entity.is_a?(Sketchup::Edge)) &&
!state.user_state.user_preferences[:register_speckle_entity]
definition = BLOCK_DEFINITION.to_native(
sketchup_model,
obj['displayValue'],
layer,
"def::#{obj_id}",
if block_definition.nil?
false
else
block_definition['always_face_camera'].nil? ? false : block_definition['always_face_camera']
end,
model_preferences,
if block_definition.nil?
nil
else
block_definition['sketchup_attributes'].nil? ? nil : block_definition['sketchup_attributes']
end,
obj_id,
&convert
)
find_and_erase_existing_instance(definition, obj_id)
t_arr = obj['transform']
transform = t_arr.nil? ? Geom::Transformation.new : OTHER::Transform.to_native(t_arr, units)
instance = entities.add_instance(definition, transform)
instance.name = obj_id
instance
ent = SpeckleEntities::SpeckleEntity.new(entity, speckle_id, application_id, speckle_type, children,
[stream_id])
ent.write_initial_base_data
speckle_state = speckle_state.with_speckle_entity(ent)
end
state.with_speckle_state(speckle_state)
end
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/MethodLength
# Takes a component definition and finds and erases the first instance with the matching name
# (and optionally the applicationId)
def find_and_erase_existing_instance(definition, name, app_id = '')
definition.instances.find { |ins| ins.name == name || ins.guid == app_id }&.erase!
end
end
# rubocop:enable Metrics/ClassLength
end
end
+81 -92
View File
@@ -4,135 +4,124 @@ require_relative 'converter'
require_relative 'base_object_serializer'
require_relative '../speckle_objects/base'
require_relative '../speckle_objects/geometry/line'
require_relative '../speckle_objects/geometry/length'
require_relative '../speckle_objects/geometry/mesh'
require_relative '../speckle_objects/other/block_instance'
require_relative '../speckle_objects/other/block_definition'
require_relative '../speckle_objects/other/rendering_options'
require_relative '../speckle_objects/built_elements/view3d'
require_relative '../speckle_objects/built_elements/revit/direct_shape'
require_relative '../speckle_objects/relations/layers'
require_relative '../speckle_objects/speckle/core/models/model_collection'
require_relative '../constants/path_constants'
require_relative '../sketchup_model/reader/speckle_entities_reader'
require_relative '../sketchup_model/query/entity'
module SpeckleConnector
module Converters
# Converts sketchup entities to speckle objects.
class ToSpeckle < Converter
# @return [Hash{Symbol=>Array}] layers to hold it's objects under the base object.
attr_reader :layers
def initialize(sketchup_model)
super(sketchup_model)
@layers = add_all_layers
end
MODEL_COLLECTION = SpeckleObjects::Speckle::Core::Models::ModelCollection
DIRECT_SHAPE = SpeckleObjects::BuiltElements::Revit::DirectShape
SPECKLE_ENTITIES_READER = SketchupModel::Reader::SpeckleEntitiesReader
VIEW3D = SpeckleObjects::BuiltElements::View3d
# 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)
sketchup_model.selection.each do |entity|
converted_object = convert(entity, preferences)
layer_name = entity_layer_path(entity)
layers[layer_name].push(converted_object)
end
convert = method(:convert)
new_speckle_state, model_collection = MODEL_COLLECTION.from_sketchup_model(sketchup_model, speckle_state,
@units, preferences, &convert)
# send only layers that have any object
base_object_properties = layers.reject { |_layer_name, objects| objects.empty? }
SpeckleObjects::Base.with_detached_layers(base_object_properties)
model_collection[:layers_relation] = SpeckleObjects::Relations::Layers.from_model(sketchup_model)
return new_speckle_state, model_collection
end
# Serialized and traversed information to send batches.
# @param base [SpeckleObjects::Base] base object to serialize.
# @param base_and_entity [SpeckleObjects::Base] base object to serialize.
# @return [String, Integer, Array<Object>] base id, total_children_count of base and batches
def send_info(base)
serializer = SpeckleConnector::Converters::BaseObjectSerializer.new
# t = Time.now.to_f
id, _traversed = serializer.serialize(base)
# puts "Generating traversed object elapsed #{Time.now.to_f - t} s"
def serialize(base_and_entity, speckle_state, preferences)
serializer = SpeckleConnector::Converters::BaseObjectSerializer.new(speckle_state, stream_id, preferences)
t = Time.now.to_f
id = serializer.serialize(base_and_entity)
batches = serializer.batch_objects
# write_to_speckle_folder(id, batches)
puts "Generating traversed object elapsed #{Time.now.to_f - t} s"
base_total_children_count = serializer.total_children_count(id)
return id, base_total_children_count, serializer.batch_objects
return id, base_total_children_count, batches, serializer.speckle_state
end
def write_to_speckle_folder(id, batches)
folder_path = "#{HOME_PATH}/Speckle"
file_path = "#{folder_path}/#{id}.json"
FileUtils.mkdir_p(folder_path) unless File.exist?(folder_path)
File.write(file_path, batches.first)
end
# @param entity [Sketchup::Entity] sketchup entity to convert Speckle.
def convert(entity, 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.
def convert(entity, preferences, speckle_state, parent = :base)
convert = method(:convert)
unless SketchupModel::Reader::SpeckleEntitiesReader.mapped_with_schema?(entity)
return from_native_to_speckle(entity, preferences, speckle_state, parent, &convert)
end
return speckle_state, nil
end
def from_mapped_to_speckle(entity, path, preferences)
direct_shape = SpeckleObjects::BuiltElements::Revit::DirectShape
.from_entity(entity, path, @units, preferences)
return [direct_shape, [entity]]
end
# rubocop:disable Metrics/MethodLength
def from_native_to_speckle(entity, preferences, speckle_state, parent, &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(face: entity, units: @units,
model_preferences: 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)
new_speckle_state, block_instance = SpeckleObjects::Other::BlockInstance.from_component_instance(
entity, @units, preferences, speckle_state, &convert
)
speckle_state = new_speckle_state
return speckle_state, [block_instance, [entity]]
end
if entity.is_a?(Sketchup::ComponentDefinition)
return SpeckleObjects::Other::BlockDefinition.from_definition(entity, @units, @definitions, preferences,
&convert)
# Local caching
return speckle_state, [definitions[entity.guid], [entity]] if definitions.key?(entity.guid)
new_speckle_state, block_definition = SpeckleObjects::Other::BlockDefinition.from_definition(
entity, @units, preferences, speckle_state, parent, &convert
)
definitions[entity.guid] = block_definition
speckle_state = new_speckle_state
return speckle_state, [block_definition, [entity]]
end
nil
end
# Create layers -> {Hash{Symbol=>Array}} from sketchup model with empty array as hash entry values.
# This method add first headless layers (not belong to any folder),
# then goes through each folder, their sub-folders and their layers.
# @return [Hash{Symbol=>Array}] layers from sketchup model with empty array as hash entry values.
def add_all_layers
# add headless layers
layer_objects = add_layers(sketchup_model.layers.layers)
# add layers from folders
add_layers_from_folders(sketchup_model.layers.folders, layer_objects)
layer_objects
end
# @param layers [Array<Sketchup::Layer>] layers in sketchup model
# @return [Hash{Symbol=>Array}] layers with empty array value.
def add_layers(layers, layer_objects = {}, parent_name = '')
layers.each do |layer|
layer_name = parent_name.empty? ? "@#{layer.display_name}" : "#{parent_name}::#{layer.display_name}"
layer_objects[layer_name] = []
end
layer_objects
end
# @param folders [Array<Sketchup::LayerFolder>] layer folders in sketchup model.
# @param layer_objects [Hash{Symbol=>Array}] layer objects to fill in.
# @param parent_name [String] parent folder name to structure layer path before send to Speckle.
# ex: "@#{parent_name}::#{layer_name}"
def add_layers_from_folders(folders, layer_objects, parent_name = '')
folders.each do |folder|
folder_name = parent_name.empty? ? "@#{folder.display_name}" : "#{parent_name}::#{folder.display_name}"
add_layers(folder.layers, layer_objects, folder_name)
add_layers_from_folders(folder.folders, layer_objects, folder_name) unless folder.folders.empty?
end
end
# Find layer path of given Sketchup entity.
# @param entity [Sketchup::Entity] entity to find root layer.
# @return [String] layer path of Sketchup entity.
def entity_layer_path(entity)
layer_name = entity.layer.display_name
if entity.layer.folder.nil?
"@#{layer_name}"
else
folders = folder_name(entity.layer.folder)
path = ''
folders.reverse.each do |folder|
path += "#{folder}::"
end
"@#{path}#{layer_name}"
end
end
# Nested method to retrieve sub-folders until nothing found.
# @return [Array<String>] folder names as list from bottom to top. Might need to be reversed if you want to see
# from top to bottom.
def folder_name(folder, folders = [])
if folder.folder.nil?
folders.push(folder.display_name)
else
folder_name(folder.folder, folders.push(folder.display_name))
end
return speckle_state, nil
end
# rubocop:enable Metrics/MethodLength
end
end
end
@@ -0,0 +1,141 @@
# frozen_string_literal: true
module SpeckleConnector
module Mapping
module Category
# Revit categories.
class RevitCategory < Hash
class << self
# rubocop:disable Metrics/MethodLength
def dictionary
{
AbutmentFoundations: 0,
AbutmentPiles: 1,
AbutmentWalls: 2,
BridgeAbutments: 3,
DuctTerminal: 4,
Alignments: 5,
StructConnectionAnchors: 6,
ApproachSlabs: 7,
BridgeArches: 8,
AudioVisualDevices: 9,
StairsRailingBaluster: 10,
BridgeBearings: 11,
StructConnectionBolts: 12,
BridgeCables: 13,
BridgeDecks: 14,
BridgeFraming: 15,
CableTrayFitting: 16,
CableTrayRun: 17,
CableTray: 18,
Casework: 19,
Ceilings: 20,
Columns: 21,
CommunicationDevices: 22,
ConduitFitting: 23,
Conduit: 24,
Coordination_Model: 25,
BridgeFramingCrossBracing: 26,
CurtainWallPanels: 27,
CurtaSystem: 28,
CurtainWallMullions: 29,
DataDevices: 30,
BridgeFramingDiaphragms: 31,
Doors: 32,
DuctAccessory: 33,
DuctFitting: 34,
PlaceHolderDucts: 35,
DuctSystem: 36,
DuctCurves: 37,
ElectricalEquipment: 38,
ElectricalFixtures: 39,
Entourage: 40,
ExpansionJoints: 41,
FireAlarmDevices: 42,
FireProtection: 43,
Floors: 44,
FoodServiceEquipment: 45,
Furniture: 46,
FurnitureSystems: 47,
GenericAnnotation: 48,
GenericModel: 49,
BridgeGirders: 50,
Hardscape: 51,
LightingDevices: 52,
LightingFixtures: 53,
Lines: 54,
Mass: 55,
MechanicalEquipment: 56,
MedicalEquipment: 57,
NurseCallDevices: 58,
Parking: 59,
Parts: 60,
PierCaps: 61,
PierColumns: 62,
BridgeFoundations: 63,
PierPiles: 64,
BridgeTowers: 65,
PierWalls: 66,
BridgePiers: 67,
PipeAccessory: 68,
PipeFitting: 69,
PlaceHolderPipes: 70,
PipeSegments: 71,
PipeCurves: 72,
PipingSystem: 73,
Planting: 74,
StructConnectionPlates: 75,
PlumbingFixtures: 76,
StructConnectionProfiles: 77,
StairsRailing: 78,
Ramps: 79,
Roads: 80,
Roofs: 81,
SecurityDevices: 82,
StructConnectionShearStuds: 83,
Signage: 84,
Site: 85,
SpecialityEquipment: 86,
Sprinklers: 87,
Stairs: 88,
StructuralFramingSystem: 89,
StructuralColumns: 90,
StructConnections: 91,
FabricAreas: 92,
StructuralFoundation: 93,
StructuralFraming: 94,
Rebar: 95,
Coupler: 96,
StructuralStiffener: 97,
StructuralTendons: 98,
StructuralTruss: 99,
TemporaryStructure: 100,
Topography: 101,
BridgeFramingTrusses: 102,
VerticalCirculation: 103,
VibrationDampers: 104,
VibrationIsolators: 105,
VibrationManagement: 106,
Walls: 107,
StructConnectionWelds: 108,
Windows: 109
}.freeze
end
# rubocop:enable Metrics/MethodLength
def reverse_dictionary
dictionary.collect { |k, v| [v, k] }.to_h
end
def to_a
dictionary.collect { |k, v| { key: k, value: v } }.to_a
end
def reverse_to_a
dictionary.collect { |k, v| { key: v, value: k } }.to_a
end
end
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,32 @@
# 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 'selection_observer'
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),
SELECTION_OBSERVER => SelectionObserver.new(handler)
}.freeze
end
end
end
end
@@ -0,0 +1,19 @@
# frozen_string_literal: true
require_relative 'event_observer'
module SpeckleConnector
module Observers
# Model related event observers.
class ModelObserver < Sketchup::ModelObserver
include EventObserver
# rubocop:disable Naming/MethodName
# @param model (Sketchup::Model)
def onActivePathChanged(model)
push_event(:onActivePathChanged, model)
end
# rubocop:enable Naming/MethodName
end
end
end
@@ -0,0 +1,68 @@
# frozen_string_literal: true
require_relative 'event_hash'
module SpeckleConnector
module Observers
# Class to handle observer events.
class ObserverHandler
# @return [EventHash] registered events
attr_reader :events
# @return [Float] time when first observer was added
attr_reader :start_time
def initialize(event_handler)
@event_handler = event_handler
clear_events!
@observers_disabled = false
@observers_in_progress = false
@timer_in_progress = false
end
def handle_event!(observer_class, event_name, data)
puts "## Register #{observer_class} event #{event_name} ##"
puts " data = #{data.inspect}"
return if observers_disabled?
@events = @events.push_event(observer_class, event_name, data)
finish
end
def finish
return if @observers_in_progress
@observers_in_progress = true
@start_time = Time.now.to_f
UI.start_timer(0, false) { finish_in_timer }
end
def finish_in_timer
return if @timer_in_progress
@timer_in_progress = true
@event_handler.handle_events(events)
ensure
clear_events!
@observers_in_progress = false
@timer_in_progress = false
end
def observers_disabled?
@observers_disabled
end
def with_observers_disabled(&block)
previous_state = @observers_disabled
@observers_disabled = true
block.call
ensure
@observers_disabled = previous_state
end
def clear_events!
@events = EMPTY_EVENT_HASH
end
end
end
end
@@ -0,0 +1,49 @@
# frozen_string_literal: true
require_relative 'event_observer'
module SpeckleConnector
module Observers
# @see https://ruby.sketchup.com/Sketchup/SelectionObserver.html
class SelectionObserver < Sketchup::SelectionObserver
include EventObserver
# rubocop:disable Naming/MethodName
# @param _selection (Sketchup::Selection)
# @param _entity (Sketchup::Entity)
def onSelectionAdded(_selection, _entity)
push_selection_event(:onSelectionAdded)
end
# @param _selection (Sketchup::Selection)
def onSelectionBulkChange(_selection)
push_selection_event(:onSelectionBulkChange)
end
# @param _selection (Sketchup::Selection)
def onSelectionCleared(_selection)
push_selection_event(:onSelectionCleared)
end
# @param _selection (Sketchup::Selection)
def onSelectionRemoved(_selection, _entity)
push_selection_event(:onSelectionRemoved)
end
# Due to a SketchUp bug, this method is called by the wrong name.
alias onSelectedRemoved onSelectionRemoved
# rubocop:enable Naming/MethodName
private
# Selection changes need to be registered only once
def push_selection_event(event_name)
# Don't push anything if the selection event was already registered
selection_events = observer_handler.events[self.class]
return if selection_events&.any?
push_event(event_name)
end
end
end
end
@@ -3,6 +3,7 @@
require_relative '../ext/sqlite3'
require_relative '../immutable/immutable'
require_relative '../constants/path_constants'
require_relative '../constants/pref_constants'
require_relative '../sketchup_model/dictionary/speckle_model_dictionary_handler'
module SpeckleConnector
@@ -10,60 +11,88 @@ module SpeckleConnector
module Preferences
include Immutable::ImmutableUtils
DICT_HANDLER = SketchupModel::Dictionary::SpeckleModelDictionaryHandler
# rubocop:disable Layout/LineLength
DEFAULT_CONFIG = "('configSketchup', '{\"dark_theme\":false, \"diffing\":false, \"register_speckle_entity\":false}');"
# rubocop:enable Layout/LineLength
DEFAULT_PREFERENCES = '{"dark_theme":false, "diffing":false, "register_speckle_entity": false}'
# @param sketchup_model [Sketchup::Model] active model.
# rubocop:disable Metrics/MethodLength
def self.init_preferences(sketchup_model)
# Init sqlite database
def self.read_preferences(sketchup_model)
db = Sqlite3::Database.new(SPECKLE_CONFIG_DB_PATH)
user_preferences = validate_user_preferences(db)
model_preferences = validate_model_preferences(sketchup_model)
Immutable::Hash.new(
{
user: user_preferences,
model: model_preferences
}
)
end
# Whether row data is complete with preference or not.
# It is useful for backward compatibility, when we add new preferences it should be reset when user initialize it.
def self.data_complete?(row_data)
return false if row_data.empty?
data = JSON.parse(row_data.first.first)
return false if data['dark_theme'].nil? || data['diffing'].nil? || data['register_speckle_entity'].nil?
true
end
# Validates current preferences. If there are incomplete data then this method resets it with default preferences.
# @param database [Sqlite3::Database] database for queries.
# rubocop:disable Metrics/MethodLength
def self.validate_user_preferences(database)
row_data = database.exec("SELECT content FROM 'objects' WHERE hash = 'configSketchup'")
is_config_sketchup_exist = !row_data.empty?
is_data_complete = data_complete?(row_data)
if !is_config_sketchup_exist || !is_data_complete
if is_config_sketchup_exist
unless is_data_complete
# Update table with default preferences
database.exec("UPDATE 'objects' SET content = '#{DEFAULT_PREFERENCES}' WHERE hash = 'configSketchup'")
end
else
# Insert configSketchup completely to objects.
database.exec("INSERT INTO 'objects' VALUES #{DEFAULT_CONFIG}")
end
end
# Select data
data = db.exec("SELECT content FROM 'objects' WHERE hash = 'configDUI'").first.first
row_data = database.exec("SELECT content FROM 'objects' WHERE hash = 'configSketchup'").first.first
# Parse string to hash
data_hash = JSON.parse(data).to_h
data_hash = JSON.parse(row_data).to_h
# Get current theme value
dark_theme = data_hash['DarkTheme']
dark_theme = data_hash['dark_theme']
diffing = data_hash['diffing']
register_speckle_entity = data_hash['register_speckle_entity']
speckle_dictionary = sketchup_model.attribute_dictionary('Speckle')
if speckle_dictionary
Immutable::Hash.new(
{
user: {
dark_theme: dark_theme
},
model: {
combine_faces_by_material: DICT_HANDLER.get_attribute(sketchup_model,
:combine_faces_by_material, 'Speckle'),
include_entity_attributes: DICT_HANDLER.get_attribute(sketchup_model,
:include_entity_attributes, 'Speckle'),
merge_coplanar_faces: DICT_HANDLER.get_attribute(sketchup_model,
:merge_coplanar_faces, 'Speckle')
}
}
)
else
DICT_HANDLER.write_initial_model_data(sketchup_model, default_model_preferences)
Immutable::Hash.new(
{
user: {
dark_theme: dark_theme
},
model: default_model_preferences
}
)
end
{
dark_theme: dark_theme,
diffing: diffing,
register_speckle_entity: register_speckle_entity
}.freeze
end
# rubocop:enable Metrics/MethodLength
def self.default_model_preferences
{
combine_faces_by_material: true,
include_entity_attributes: true,
merge_coplanar_faces: true
}
# @param sketchup_model [Sketchup::Model] sketchup model to validate model preferences
def self.validate_model_preferences(sketchup_model)
speckle_dictionary = sketchup_model.attribute_dictionary('Speckle')
if speckle_dictionary.nil?
DICT_HANDLER.write_initial_model_data(sketchup_model, DEFAULT_MODEL_PREFERENCES)
return DEFAULT_MODEL_PREFERENCES
end
DEFAULT_MODEL_PREFERENCES.collect do |pref_key, default_value|
pref_value = DICT_HANDLER.get_attribute(
sketchup_model,
pref_key,
'Speckle'
)
DICT_HANDLER.set_attribute(sketchup_model, pref_key, default_value, 'Speckle') if pref_value.nil?
pref_value.nil? ? [pref_key, default_value] : [pref_key, pref_value]
end.to_h
end
end
end
@@ -8,6 +8,8 @@ module SpeckleConnector
# `A has child B` is Many to One relationship. Each person can have only one parent,
# but one parent can have several children.
class ManyToOneRelation
attr_reader :parent_table, :children_table
# @param child [Object] the child element in the relation to look for parent
# @return [Object] the parent element that is in relation with the child
# @return [nil] if the child element is not in the relation with any elements
@@ -0,0 +1,47 @@
# frozen_string_literal: true
require_relative '../../constants/dict_constants'
module SpeckleConnector
# Operations related to {SketchupModel}.
module SketchupModel
# Definitions to store for entities.
class Definitions
def self.from_sketchup_model(model)
definitions = model.definitions
definitions = definitions.select do |definition|
!definition.attribute_dictionaries.nil? &&
definition.attribute_dictionaries.any? { |dict| dict.name == SPECKLE_BASE_OBJECT }
end
Definitions.new(definitions)
end
def add_definition(definition)
old_definition = @definitions_by_guid[definition.guid]
return self if definition == old_definition
new_definitions = @definitions.append(definition)
Definitions.new(new_definitions)
end
def initialize(definitions = [])
@definitions = definitions
@definitions_by_name = definitions.collect do |definition|
[definition.name, definition]
end.to_h.freeze
@definitions_by_guid = definitions.collect do |definition|
[definition.guid, definition]
end.to_h.freeze
freeze
end
def by_guid(guid)
@definitions_by_guid[guid]
end
def by_name(name)
@definitions_by_name[name]
end
end
end
end
@@ -0,0 +1,89 @@
# frozen_string_literal: true
require 'delegate'
require_relative 'dictionary_handler'
require_relative '../../constants/dict_constants'
module SpeckleConnector
module SketchupModel
module Dictionary
# Read and write attributes for Speckle objects on SketchUp model.
class BaseDictionaryHandler < DictionaryHandler
IGNORED_DICTIONARY_NAMES = [
SPECKLE_BASE_OBJECT,
'IFC 4',
'IFC 2x3'
].freeze
# @param entity [Sketchup::Entity] entity to get attribute dictionaries
def self.attribute_dictionaries_to_speckle(entity, model_preferences)
dictionaries = {}
return dictionaries unless model_preferences[INCLUDE_ENTITY_ATTRIBUTES]
klass = get_entity_setting_type(entity)
return dictionaries unless model_preferences[ENTITY_KEYS_FOR_INCLUDING_ATTRIBUTES[klass]]
return dictionaries if entity.attribute_dictionaries.nil?
entity.attribute_dictionaries.each do |att_dict|
dict_name = att_dict == '' ? 'empty_dictionary_name' : att_dict.name
dictionaries[dict_name] = att_dict.to_h unless IGNORED_DICTIONARY_NAMES.include?(att_dict.name)
end
dictionaries
end
# @param entity [Sketchup::Entity] entity to set attribute dictionaries
# rubocop:disable Metrics/CyclomaticComplexity
def self.attribute_dictionaries_to_native(entity, dictionaries)
return if dictionaries.nil?
classification_to_native(entity, dictionaries) if entity.is_a?(Sketchup::ComponentDefinition)
dictionaries.each do |dict_name, entries|
next unless entries.is_a?(Hash)
dict_name = dict_name == 'empty_dictionary_name' ? '' : dict_name
entries.each do |key, value|
set_attribute(entity, key, value, dict_name)
rescue StandardError => e
puts("Failed to write key: #{key} value: #{value} to dictionary #{dict_name}")
puts(e)
end
end
end
# rubocop:enable Metrics/CyclomaticComplexity
# Classification is ComponentDefinition specific, so they can be added only definition by add_classification
# method.
# @param definition_entity [Sketchup::ComponentDefinition] definition to add callback
def self.classification_to_native(definition_entity, dictionaries)
applied_schema_types = dictionaries['AppliedSchemaTypes']
return if applied_schema_types.nil?
applied_schema_types.each do |key, value|
definition_entity.add_classification(key, value)
end
end
# @return [String] the name of the dictionary to read from
def self.dictionary_name
SPECKLE_BASE_OBJECT
end
# Gets entity type for including entity attributes setting.
# @param entity [Sketchup::Entity] entity to find setting entity.
# @return [Sketchup::Face, Sketchup::Edge, Sketchup::Group, Sketchup::ComponentInstance]
def self.get_entity_setting_type(entity)
klass = entity.class
if entity.is_a?(Sketchup::ComponentDefinition)
klass = if entity.group?
Sketchup::Group
else
Sketchup::ComponentInstance
end
end
klass
end
end
end
end
end
@@ -1,38 +1,13 @@
# frozen_string_literal: true
require 'delegate'
require_relative '../../constants/dict_constants'
module SpeckleConnector
module SketchupModel
module Dictionary
# Read and write attributes from the groups and other entities that represents Speckle objects on SketchUp model.
class DictionaryHandler
DICTIONARY_NAME = 'Speckle_Base_Object'
# @param entity [Sketchup::Entity] entity to get attribute dictionaries
def self.attribute_dictionaries_to_speckle(entity)
dictionaries = {}
return dictionaries if entity.attribute_dictionaries.nil?
entity.attribute_dictionaries.each do |att_dict|
dict_name = att_dict == '' ? 'empty_dictionary_name' : att_dict.name
dictionaries[dict_name] = att_dict.to_h.to_json unless att_dict.name == 'Speckle_Base_Object'
end
dictionaries
end
# @param entity [Sketchup::Entity] entity to set attribute dictionaries
def self.attribute_dictionaries_to_native(entity, dictionaries)
return if dictionaries.nil?
dictionaries.each do |dict_name, entries|
dict_name = dict_name == 'empty_dictionary_name' ? '' : dict_name
JSON.parse(entries).each do |key, value|
entity.set_attribute(dict_name, key, value)
end
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
@@ -78,9 +53,15 @@ module SpeckleConnector
dictionary.delete_key(key)
end
# @param entity [Sketchup::Entity] the sketchup entity of Speckle object
# @param dictionary_name [String, Symbol] the name of the attribute dictionary to remove
def self.remove_dictionary(entity, dictionary_name = self.dictionary_name)
entity.attribute_dictionaries.delete(dictionary_name)
end
# @return [String] the name of the dictionary to read from
def self.dictionary_name
DICTIONARY_NAME
raise NotImplementedError 'Implement this in subclass'
end
end
end
@@ -1,6 +1,6 @@
# frozen_string_literal: true
require_relative 'dictionary_handler'
require_relative 'base_dictionary_handler'
require_relative '../../constants/dict_constants'
require_relative '../../constants/type_constants'
@@ -9,18 +9,30 @@ module SpeckleConnector
module Dictionary
# Dictionary handler of the speckle entity.
class SpeckleEntityDictionaryHandler < DictionaryHandler
DICTIONARY_NAME = SPECKLE_BASE_OBJECT
# Writes initial data while speckle entity is creating first time.
# @param sketchup_entity [Sketchup::Entity] Sketchup entity to write data into it's attribute dictionary.
def self.write_initial_base_data(sketchup_entity)
# rubocop:disable Metrics/ParameterLists
def self.write_initial_base_data(sketchup_entity, application_id, id, speckle_type, children, stream_id)
initial_dict_data = {
# Add here more if you want to write here initial data
SPECKLE_ID => '',
SPECKLE_TYPE => BASE_OBJECT,
APPLICATION_ID => '',
TOTAL_CHILDREN_COUNT => 0
SPECKLE_ID => id,
APPLICATION_ID => application_id,
SPECKLE_TYPE => speckle_type,
TOTAL_CHILDREN_COUNT => children.length,
CHILDREN => children,
VALID_STREAM_IDS => [stream_id],
INVALID_STREAM_IDS => []
}
set_hash(sketchup_entity, initial_dict_data)
end
# rubocop:enable Metrics/ParameterLists
# @return [String] the name of the dictionary to read from
def self.dictionary_name
DICTIONARY_NAME
end
end
end
end
@@ -1,6 +1,6 @@
# frozen_string_literal: true
require_relative 'dictionary_handler'
require_relative 'base_dictionary_handler'
require_relative '../../constants/dict_constants'
require_relative '../../constants/type_constants'
@@ -8,7 +8,7 @@ module SpeckleConnector
module SketchupModel
module Dictionary
# Dictionary handler of the speckle model.
class SpeckleModelDictionaryHandler < DictionaryHandler
class SpeckleModelDictionaryHandler < BaseDictionaryHandler
DICTIONARY_NAME = 'Speckle'
# Writes initial data while speckle entity is creating first time.
# @param sketchup_model [Sketchup::Model] Sketchup model to write data into it's attribute dictionary.
@@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'delegate'
require_relative 'dictionary_handler'
require_relative '../../constants/dict_constants'
module SpeckleConnector
module SketchupModel
module Dictionary
# Read and write attributes for Speckle objects' schema on SketchUp model.
class SpeckleSchemaDictionaryHandler < DictionaryHandler
def self.speckle_schema_to_speckle(entity)
schema = {}
return schema if entity.attribute_dictionaries.nil?
schema_dict = entity.attribute_dictionaries.find { |dict| dict.name == dictionary_name }
return schema if schema_dict.nil?
schema_dict.to_h
end
# @return [String] the name of the dictionary to read from
def self.dictionary_name
SPECKLE_MAPPING_TOOL_SCHEMA
end
end
end
end
end
@@ -0,0 +1,55 @@
# frozen_string_literal: true
require_relative '../../constants/mat_constants'
module SpeckleConnector
# Operations related to {SketchupModel}.
module SketchupModel
# Materials to store for entities.
class Materials
def self.from_sketchup_model(model)
materials = model.materials
materials = materials.select do |material|
!material.attribute_dictionaries.nil? &&
material.attribute_dictionaries.any? { |dict| dict.name == MAT_DICTIONARY }
end
mat_hash = materials.collect { |material| [material.get_attribute(MAT_DICTIONARY, MAT_ID), material] }.to_h
Materials.new(mat_hash)
end
def initialize(material_hash = {})
@materials_by_id = material_hash.freeze
@id_by_materials = material_hash.collect { |id, material| [material, id] }.to_h.freeze
freeze
end
def add_material(id, material)
old_material = @materials_by_id[id.to_sym]
return self if material == old_material
new_material_hash = @materials_by_id.merge({ id.to_sym => material })
Materials.new(new_material_hash)
end
def by_id(id)
@materials_by_id[id.to_sym]
end
def material_id(material)
@id_by_materials[material]
end
def add_speckle_material
@materials_by_id[MAT_ADD]
end
def edit_speckle_material
@materials_by_id[MAT_EDIT]
end
def remove_speckle_material
@materials_by_id[MAT_REMOVE]
end
end
end
end
@@ -0,0 +1,113 @@
# frozen_string_literal: true
module SpeckleConnector
module SketchupModel
# Query operations in sketchup model.
module Query
# Queries for entity.
class Entity
class << self
# Creates flat list for entities that defined in classes property. It searches from top to bottom to collect
# entities.
# @param entities_to_flat [Sketchup::Entities] entities to flat their children, grandchildren and so on..
# @param classes [Array<Class>] objects types to collect as flat list.
def flat_entities(entities_to_flat,
classes = [Sketchup::Edge, Sketchup::Face, Sketchup::ComponentInstance,
Sketchup::Group, Sketchup::ComponentDefinition])
entities = []
entities_to_flat.each do |entity|
entities.append(entity) if classes.include?(entity.class)
if entity.is_a?(Sketchup::Group) || entity.is_a?(Sketchup::ComponentInstance)
entities.append(entity.definition) if classes.include?(Sketchup::ComponentDefinition)
entities += flat_entities(entity.definition.entities, classes)
end
end
entities
end
# Create array for each entity with their path.
# @param entities_to_flat [Sketchup::Entities, Array<Sketchup::Entity>] entities to flat with their path.
# @param classes [Array<Class>] classes to flat. Put class into this array if you want to find their paths.
# @param path [Array<Object>] path for entity that we are in.
# @return [Array<Object>] entity with it's path as flat array. See example.
# @example
# path[0] is entity itself
# path[1..-1] rest as path from top to bottom
def flat_entities_with_path(entities_to_flat,
classes = [Sketchup::Edge, Sketchup::Face, Sketchup::ComponentInstance,
Sketchup::Group, Sketchup::ComponentDefinition],
path = [])
entities = []
entities_to_flat.each do |entity|
# Collect object itself
entities.append([entity] + path) if classes.include?(entity.class)
# entities[entity] = path if classes.include?(entity.class) && entities[entity].nil?
# Skip unless entity is a container entity like group or component.
next unless entity.is_a?(Sketchup::Group) || entity.is_a?(Sketchup::ComponentInstance)
# Add entity definition also with it's path.
entities[entity.definition] = path if classes.include?(Sketchup::ComponentDefinition)
# Collect sub-objects if object is a container at the same time.
sub_entities = flat_entities_with_path(entity.definition.entities.to_a,
classes, path + [entity])
entities += sub_entities
end
entities
end
# Calculates global transformation of entity by multiplying path entries from bottom to top by reversing path.
# @param entity [Sketchup::Entity] entity to find global transformation.
# @param path [Array<Object>] path that parents of entity that has transformation value to calculate global
# transformation of the entity.
# @return [Geom::Transformation] global transformation of the entity.
def global_transformation(entity, path)
# If entity is face, use Identity
global = entity.respond_to?(:transformation) ? entity.transformation : Geom::Transformation.new
path.reverse.each do |local|
global = local.transformation * global if local.respond_to?(:transformation)
end
global
end
# Global transformation search for entity that lies on only one instance.
# @param entity [Sketchup::Entity] entity to find global transformation.
def global_transformation_from_bottom(entity)
# If entity is face, use Identity
transformation = entity.respond_to?(:transformation) ? entity.transformation : Geom::Transformation.new
parent = parent_or_model(entity)
until parent.is_a?(Sketchup::Model) || parent.nil?
transformation = parent.transformation * transformation
parent = parent_or_model(parent)
end
transformation
end
# Parent search for entity from bottom to top. It is not ideal if entity lives in different instances.
def parent_or_model(entity)
parent = entity.parent
return parent if parent.is_a?(Sketchup::Model)
instances = parent.instances
if instances.length > 1
puts 'Parent has more than one instance'
instances.each(&:make_unique)
instances = instances.select { |instance| instance.definition.entities.include?(entity) }
end
instances.first
end
# Finds first material of parents from bottom to top.
def parent_material(path)
material = nil
path.reverse.each do |local|
material = local.material if local.respond_to?(:material)
return material unless material.nil?
end
material
end
end
end
end
end
end
@@ -0,0 +1,25 @@
# frozen_string_literal: true
module SpeckleConnector
module SketchupModel
# Query operations in sketchup model.
module Query
# Queries for layer and it's parents.
class Layer
class << self
# @param layer [Sketchup::Layer] layer to get folder path of the layer
# @return [Array<Sketchup::Folder>] path of the layer
def path(layer)
parent_folders = []
folder = layer.folder
until folder.nil?
parent_folders.append(folder)
folder = folder.folder
end
parent_folders.reverse
end
end
end
end
end
end
@@ -0,0 +1,123 @@
# frozen_string_literal: true
require_relative '../dictionary/speckle_schema_dictionary_handler'
require_relative '../../speckle_entities/speckle_entity'
require_relative '../../mapping/category/revit_category'
require_relative '../../constants/dict_constants'
module SpeckleConnector
# Operations related to {SketchupModel}.
module SketchupModel
# Reader model for sketchup model.
module Reader
# Reader module for speckle entities.
module SpeckleEntitiesReader
# @param entities [Sketchup::Entities] entities to collect speckle entities.
# @return [Hash{String=>Sketchup::Entity}] speckle entities with persistent id.
def self.read(entities)
speckle_entities = {}
entities.each do |entity|
speckle_entities[entity.persistent_id] = read_speckle_entity(entity) if speckle_entity?(entity)
next unless entity.is_a?(Sketchup::Group) || entity.is_a?(Sketchup::ComponentInstance)
if speckle_entity?(entity.definition)
speckle_entities[entity.definition.persistent_id] = read_speckle_entity(entity.definition)
end
definition_speckle_entities = read(entity.definition.entities)
speckle_entities = speckle_entities.merge(definition_speckle_entities)
end
speckle_entities
end
# @param entities [Sketchup::Entities] entities to collect mapped entities.
# @return [Hash{String=>Sketchup::Entity}] mapped entities with persistent id.
def self.read_mapped_entities(entities)
mapped_entities = {}
Query::Entity.flat_entities(entities).each do |entity|
mapped_entities[entity.persistent_id] = entity if mapped_with_schema?(entity)
end
mapped_entities
end
# @param entity [Sketchup::Entity] sketchup entity to read from attribute dictionary.
def self.read_speckle_entity(entity)
dict = entity.attribute_dictionaries.to_a.find { |d| d.name == SPECKLE_BASE_OBJECT }
speckle_id = dict[:speckle_id]
application_id = dict[:application_id]
speckle_type = dict[:speckle_type]
children = dict[:children]
valid_stream_ids = dict[:valid_stream_ids]
invalid_stream_ids = dict[:invalid_stream_ids]
SpeckleEntities::SpeckleEntity.new(entity, speckle_id, application_id, speckle_type, children,
valid_stream_ids, invalid_stream_ids)
end
# @param entity [Sketchup::Entity] sketchup entity to check if it was speckle entity once.
def self.speckle_entity?(entity)
return false if entity.attribute_dictionaries.nil?
return false if entity.attribute_dictionaries.to_a.empty?
entity.attribute_dictionaries.to_a.any? { |dict| dict.name == SPECKLE_BASE_OBJECT }
end
# @param entity [Sketchup::Entity] sketchup entity to check whether mapped with speckle schema or not.
def self.mapped_with_schema?(entity)
is_entity_mapped = !Dictionary::SpeckleSchemaDictionaryHandler.attribute_dictionary(entity).nil?
return is_entity_mapped if is_entity_mapped
return is_entity_mapped unless entity.is_a?(Sketchup::ComponentInstance)
!Dictionary::SpeckleSchemaDictionaryHandler.attribute_dictionary(entity.definition).nil?
end
def self.get_schema(entity)
Dictionary::SpeckleSchemaDictionaryHandler.speckle_schema_to_speckle(entity)
end
def self.entities_schema_details(entities)
entities.collect do |entity|
entity_selection_details = entity_selection_details(entity)
if entity.is_a?(Sketchup::ComponentInstance)
entity_selection_details = entity_selection_details.merge(
{ definition: entity_selection_details(entity.definition) }
)
end
entity_selection_details
end
end
def self.entity_selection_details(entity)
sanitized_type = entity.class.name.split('::').last.gsub(/(?<=[a-z])(?=[A-Z])/, ' ').split
is_definition = entity.is_a?(Sketchup::ComponentDefinition)
entity_type = is_definition ? sanitized_type.last : sanitized_type.first
speckle_schema = get_schema(entity)
{
name: speckle_schema['name'],
entityName: entity.respond_to?(:name) ? entity.name : '',
entityId: entity.persistent_id,
entityType: entity_type,
schema: speckle_schema,
numberOfInstances: is_definition ? entity.instances.length : 1
}
end
def self.mapped_entity_details(entities)
reverse_category_dictionary = Mapping::Category::RevitCategory.reverse_dictionary
entities.collect do |entity|
speckle_schema = get_schema(entity)
{
name: speckle_schema['name'],
category: speckle_schema['category'],
categoryName: reverse_category_dictionary[speckle_schema['category']],
method: speckle_schema['method'],
entityName: entity.respond_to?(:name) ? entity.name : '',
entityId: entity.persistent_id,
entityType: entity.class.name.split('::').last.gsub(/(?<=[a-z])(?=[A-Z])/, ' ').split.first,
schema: speckle_schema,
definitionSchema: entity.respond_to?(:definition) ? get_schema(entity.definition) : nil
}
end
end
end
end
end
end
@@ -0,0 +1,30 @@
# frozen_string_literal: true
module SpeckleConnector
# Operations related to {SketchupModel}.
module SketchupModel
# Works directly with/on SketchUp Entities of different kinds (Groups, Faces, Edges, ...).
module Utils
# Static methods that work directly with Sketchup::Face objects
class FaceUtils
# A method that calculates the faces that are less than n-faces
# away from the current face. For n=1, you would get the
# neighbouring faces.
# @param edge_ary [Array<Sketchup::Edge>] the edges to look for n-adjacent faces
# @param n_adjacent [Integer] the distance from the edges (in number of faces)
# @return [Array<Sketchup::Face>] the faces that are n faces away from the given edges
def self.near_faces(edges_ary, n_adjacent = 1)
# get all the faces that are not more than a face away to the current face.
edges_ary = edges_ary.select(&:valid?)
adj_faces = []
n_adjacent.times do |_i|
new_faces = edges_ary.collect(&:faces).flatten.uniq
adj_faces += new_faces
edges_ary = new_faces.collect(&:edges).flatten.uniq - edges_ary
end
adj_faces.uniq
end
end
end
end
end
@@ -1,38 +0,0 @@
# frozen_string_literal: true
require_relative '../immutable/immutable'
require_relative '../convertors/units'
require_relative '../speckle_objects/base'
require_relative '../speckle_entities/speckle_line_entity'
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
module SpeckleConnector
module SpeckleEntities
# Speckle base entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
class SpeckleBaseEntity
include Immutable::ImmutableUtils
# @return [Sketchup::Entity] sketchup entity represents {SpeckleEntity} on the model.
attr_reader :sketchup_entity
# @return [SpeckleObjects::Base] speckle object that represented on server.
attr_reader :speckle_object
# @return [Integer] application id of the sketchup entity.
attr_reader :application_id
# @param sketchup_entity [Sketchup::Entity] sketchup entity represents {SpeckleEntity} on the model.
def initialize(sketchup_model, sketchup_entity)
@sketchup_entity = sketchup_entity
@application_id = @sketchup_entity.persistent_id
@speckle_object = SpeckleObjects::Base.new
su_unit = sketchup_model.options['UnitsOptions']['LengthUnit']
@units = Converters::SKETCHUP_UNITS[su_unit]
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.write_initial_base_data(@sketchup_entity)
end
def valid?
sketchup_entity.valid?
end
end
end
end
@@ -1,19 +1,123 @@
# frozen_string_literal: true
require_relative '../speckle_entities/speckle_line_entity'
require_relative 'speckle_entity_status'
require_relative '../immutable/immutable'
require_relative '../convertors/units'
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
module SpeckleConnector
module SpeckleEntities
# Speckle entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
module SpeckleEntity
def self.with_converted(skp_model, skp_entity)
# return the same object if it is already SpeckleEntity
return skp_entity if skp_entity.is_a?(SpeckleEntity)
return SpeckleBlockEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::Group)
return SpeckleBlockEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::ComponentInstance)
return SpeckleMeshEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::Face)
# Speckle base entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
class SpeckleEntity
include Immutable::ImmutableUtils
# @return [Sketchup::Entity] Sketchup Entity represents {SpeckleEntity} on the model.
attr_reader :sketchup_entity
SpeckleLineEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::Edge)
# @return [String] Speckle object type.
attr_reader :speckle_type
# @return [Integer] application id of the Sketchup Entity.
attr_reader :application_id
# @return [String] id of the Speckle Base Object
attr_reader :id
# @return [Integer] total children count of the Speckle Base Object
attr_reader :total_children_count
# @return [Hash{String=>SpeckleObjects::Base}] Speckle objects belongs to edge
attr_reader :speckle_children_objects
# @return [Array<String>] speckle entity that valid on streams
attr_reader :valid_stream_ids
# @return [Array<String>] speckle entity that invalid on streams
attr_reader :invalid_stream_ids
# @return [SpeckleEntityStatus] current status of the Speckle Entity.
attr_reader :status
attr_reader :source_material, :active_diffing_stream_id
# @param sketchup_entity [Sketchup::Entity] sketchup entity represents {SpeckleEntity} on the model.
# rubocop:disable Metrics/ParameterLists
def initialize(sketchup_entity, speckle_id, application_id, speckle_type, children, valid_stream_ids,
invalid_stream_ids = [])
@status = SpeckleEntityStatus::UP_TO_DATE
@source_material = sketchup_entity.material
@active_diffing_stream_id = nil
@valid_stream_ids = valid_stream_ids
@invalid_stream_ids = invalid_stream_ids
@sketchup_entity = sketchup_entity
@application_id = application_id
@id = speckle_id
@total_children_count = children.nil? ? 0 : children.length
@speckle_type = speckle_type
@speckle_children_objects = children.nil? ? [] : children
end
# rubocop:enable Metrics/ParameterLists
def write_initial_base_data
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler
.write_initial_base_data(@sketchup_entity, application_id, id, speckle_type,
@speckle_children_objects, valid_stream_ids.first)
end
def with_up_to_date
with(:@status => SpeckleEntityStatus::UP_TO_DATE)
end
def with_invalid
valid = valid_stream_ids
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, VALID_STREAM_IDS, [])
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, INVALID_STREAM_IDS, valid)
with(:@valid_stream_ids => [], :@invalid_stream_ids => valid)
end
def activate_diffing(stream_id, material)
sketchup_entity.material = material
with(:@active_diffing_stream_id => stream_id)
end
def deactivate_diffing
sketchup_entity.material = @source_material
with(:@active_diffing_stream_id => nil)
end
def change_material(material)
sketchup_entity.material = material
end
def revert_material
sketchup_entity.material = source_material
end
def with_valid_stream_id(stream_id)
return self if valid_stream_ids.include?(stream_id)
invalid_ids = @invalid_stream_ids.include?(stream_id) ? @invalid_stream_ids - [stream_id] : @invalid_stream_ids
valid_ids = valid_stream_ids + [stream_id]
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, VALID_STREAM_IDS, valid_ids)
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, INVALID_STREAM_IDS, invalid_ids)
# if sketchup_entity.is_a?(Sketchup::Group) || sketchup_entity.is_a?(Sketchup::ComponentInstance)
# sketchup_entity.definition.set_attribute(SPECKLE_BASE_OBJECT, VALID_STREAM_IDS, valid_ids)
# sketchup_entity.definition.set_attribute(SPECKLE_BASE_OBJECT, INVALID_STREAM_IDS, invalid_ids)
# end
with(:@valid_stream_ids => @valid_stream_ids + [stream_id], :@invalid_stream_ids => invalid_ids)
end
def with_edited
with(:@status => SpeckleEntityStatus::EDITED)
end
def with_removed
with(:@status => SpeckleEntityStatus::REMOVED)
end
def valid?
sketchup_entity.valid?
end
end
end
@@ -0,0 +1,18 @@
# frozen_string_literal: true
require_relative '../immutable/immutable'
require_relative '../convertors/units'
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
module SpeckleConnector
module SpeckleEntities
module SpeckleEntityStatus
# Speckle Entity created first time with {Sketchup::Entity} before sent to server.
UP_TO_DATE = 0
# {Sketchup::Entity} that corresponds to Speckle Entity is edited after created by user.
EDITED = 1
# {Sketchup::Entity} that corresponds to Speckle Entity is removed.
REMOVED = 2
end
end
end
@@ -1,25 +0,0 @@
# frozen_string_literal: true
require_relative 'speckle_base_entity'
require_relative '../immutable/immutable'
require_relative '../speckle_objects/geometry/line'
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
module SpeckleConnector
module SpeckleEntities
# Speckle line entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
class SpeckleLineEntity < SpeckleBaseEntity
include Immutable::ImmutableUtils
# @return [SpeckleObjects::Geometry::Line] speckle line object
attr_reader :speckle_object
def initialize(sketchup_model, sketchup_edge)
super(sketchup_model, sketchup_edge)
@speckle_object = SpeckleObjects::Geometry::Line.from_edge(sketchup_edge, units)
end
alias sketchup_edge sketchup_entity
end
end
end
@@ -0,0 +1,105 @@
# frozen_string_literal: true
require_relative '../../base'
require_relative '../../other/render_material'
require_relative '../../../constants/type_constants'
require_relative '../../../sketchup_model/query/entity'
require_relative '../../../sketchup_model/reader/speckle_entities_reader'
module SpeckleConnector
module SpeckleObjects
module BuiltElements
module Revit
# Direct shape definition for Revit mappings.
class DirectShape < Base
SPECKLE_TYPE = OBJECTS_BUILTELEMENTS_REVIT_DIRECTSHAPE
READER = SketchupModel::Reader
QUERY = SketchupModel::Query
DICTIONARY = SketchupModel::Dictionary
def initialize(name:, category:, units:, base_geometries:, application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: application_id,
id: nil
)
self[:name] = name
self[:category] = category
self[:units] = units
self[:baseGeometries] = base_geometries
end
# Collects direct shapes on selection as flat list.
def self.direct_shapes_on_selection(sketchup_model)
flat_selection_with_path = QUERY::Entity.flat_entities_with_path(
sketchup_model.selection,
[Sketchup::Face, Sketchup::ComponentInstance, Sketchup::Group], [sketchup_model]
)
mapped_selection = []
flat_selection_with_path.each do |entities|
entity = entities[0]
is_entity_mapped = READER::SpeckleEntitiesReader.mapped_with_schema?(entity)
if entity.respond_to?(:definition)
is_definition_mapped = READER::SpeckleEntitiesReader.mapped_with_schema?(entity.definition)
mapped_selection.append(entities) if is_entity_mapped || is_definition_mapped
next
end
mapped_selection.append(entities) if is_entity_mapped
end
mapped_selection
end
def self.from_entity(entity, path, units, preferences)
schema = DICTIONARY::SpeckleSchemaDictionaryHandler.attribute_dictionary(entity)
if schema.nil? && entity.respond_to?(:definition)
schema = DICTIONARY::SpeckleSchemaDictionaryHandler.attribute_dictionary(entity.definition)
end
entities_with_path = []
entities_with_path.append([entity] + path) if entity.is_a?(Sketchup::Face) || entity.is_a?(Sketchup::Edge)
# Collect here flat list
if entity.is_a?(Sketchup::ComponentInstance) || entity.is_a?(Sketchup::Group)
entities_with_path += QUERY::Entity
.flat_entities_with_path(
entity.definition.entities, [Sketchup::Face], path.append(entity)
)
end
base_geometries = group_faces_under_mesh_by_material(entities_with_path, units, preferences)
DirectShape.new(
name: schema[:name], category: schema[:category], units: units,
base_geometries: base_geometries, application_id: entity.persistent_id
)
end
# rubocop:disable Metrics/MethodLength
def self.group_faces_under_mesh_by_material(faces_with_path, units, preferences)
mesh_groups = {}
faces_with_path.each do |face_with_path|
face = face_with_path[0]
entity_path = face_with_path[1..-1]
parent_material = QUERY::Entity.parent_material(entity_path)
mesh_group_id = Geometry::Mesh.get_mesh_group_id(face, preferences[:model], parent_material)
if mesh_groups.key?(mesh_group_id)
mesh_group = mesh_groups[mesh_group_id]
mesh_group[0].face_to_mesh(face, QUERY::Entity.global_transformation(face, entity_path))
mesh_group[1].append(face)
else
mesh = Geometry::Mesh.from_face(
face: face, units: units, model_preferences: preferences[:model],
global_transform: QUERY::Entity.global_transformation(face, entity_path),
parent_material: parent_material
)
mesh_groups[mesh_group_id] = [mesh, [face]]
end
end
# Update mesh overwrites points and polygons into base object.
mesh_groups.each { |_, mesh| mesh.first.update_mesh }
mesh_groups.values
end
# rubocop:enable Metrics/MethodLength
end
end
end
end
end
@@ -0,0 +1,164 @@
# frozen_string_literal: true
require_relative '../base'
require_relative '../../constants/type_constants'
require_relative '../../speckle_objects/geometry/length'
require_relative '../../speckle_objects/geometry/point'
require_relative '../../speckle_objects/geometry/vector'
module SpeckleConnector
module SpeckleObjects
module BuiltElements
# View3d object represents scenes on Sketchup.
class View3d < Base
SPECKLE_TYPE = OBJECTS_BUILTELEMENTS_VIEW3D
# @param name [String] name of the scene
# @param origin [SpeckleObjects::Geometry::Point] origin (eye) of the view.
# @param target [SpeckleObjects::Geometry::Point] target of the view.
# @param direction [SpeckleObjects::Geometry::Vector] direction of the view from eye to target.
# @param up_direction [SpeckleObjects::Geometry::Vector] up direction of the view.
# @param is_perspective [Boolean] whether view is perspective or not.
# @param lens [Boolean] fov value of the view camera.
# @param units [String] units of the camera.
# @param application_id [String] application_id of the view.
# @param update_properties [Hash{Symbol=>boolean}] properties of the view.
# @param rendering_options [Hash{Symbol=>boolean}] rendering options of the view.
# rubocop:disable Metrics/ParameterLists
def initialize(name, origin, target, direction, up_direction,
is_perspective, lens, units, application_id, update_properties, rendering_options)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: application_id,
id: nil
)
self[:name] = name
self[:origin] = origin
self[:target] = target
self[:forwardDirection] = direction
self[:upDirection] = up_direction
self[:isOrthogonal] = !is_perspective
self[:lens] = lens
self[:units] = units
self[:update_properties] = update_properties
self[:rendering_options] = rendering_options
end
# rubocop:enable Metrics/ParameterLists
# Collects scenes as views from sketchup model.
# @param sketchup_model [Sketchup::Model] sketchup model to collect views from pages.
# @param units [String] units of the model.
def self.from_model(sketchup_model, units)
sketchup_model.pages.collect { |page| from_page(page, units) }
end
# @param page [Sketchup::Page] page to convert speckle view.
def self.from_page(page, units)
cam = page.camera
origin = get_camera_origin(cam, units)
target = get_camera_target(cam, units)
direction = get_camera_direction(cam, units)
update_properties = get_scene_update_properties(page)
rendering_options = SpeckleObjects::Others::RenderingOptions.to_speckle(page.rendering_options)
View3d.new(
page.name, origin, target, direction, SpeckleObjects::Geometry::Vector.new(0, 0, 1, units),
cam.perspective?, cam.fov, units, page.name, update_properties, rendering_options
)
end
# @param state [States::State] state of the speckle app.
# @param obj [Hash] commit object.
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/CyclomaticComplexity
def self.to_native(state, view, _entities, &_convert_to_native)
sketchup_model = state.sketchup_state.sketchup_model
return state, [] unless view['speckle_type'] == 'Objects.BuiltElements.View:Objects.BuiltElements.View3D'
name = view['name'] || view['id']
return state, [] if sketchup_model.pages.any? { |page| page.name == name }
origin = view['origin']
target = view['target']
lens = view['lens'] || 50
origin = SpeckleObjects::Geometry::Point.to_native(origin['x'], origin['y'], origin['z'], origin['units'])
target = SpeckleObjects::Geometry::Point.to_native(target['x'], target['y'], target['z'], target['units'])
# Set camera position before creating scene on it.
my_camera = Sketchup::Camera.new(origin, target, [0, 0, 1], !view['isOrthogonal'], lens)
sketchup_model.active_view.camera = my_camera
sketchup_model.pages.add(name)
page = sketchup_model.pages[name]
set_page_update_properties(page, view['update_properties']) if view['update_properties']
set_rendering_options(page.rendering_options, view['rendering_options']) if view['rendering_options']
return state, [page]
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/CyclomaticComplexity
# @param page [Sketchup::Page] scene to update -update properties-
def self.set_page_update_properties(page, update_properties)
update_properties.each do |prop, value|
page.instance_variable_set(:"@#{prop}", value)
end
end
# @param rendering_options [Sketchup::RenderingOptions] rendering options of scene (page)
def self.set_rendering_options(rendering_options, speckle_rendering_options)
speckle_rendering_options.each do |prop, value|
next if rendering_options[prop].nil?
rendering_options[prop] = if value.is_a?(Hash)
SpeckleObjects::Others::Color.to_native(value)
else
value
end
end
end
# Get scene properties
# @param page [Sketchup::Page] page on sketchup.
def self.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
def self.get_camera_direction(camera, units)
SpeckleObjects::Geometry::Vector.new(
SpeckleObjects::Geometry.length_to_speckle(camera.direction[0], units),
SpeckleObjects::Geometry.length_to_speckle(camera.direction[1], units),
SpeckleObjects::Geometry.length_to_speckle(camera.direction[2], units),
units
)
end
def self.get_camera_target(camera, units)
SpeckleObjects::Geometry::Point.new(
SpeckleObjects::Geometry.length_to_speckle(camera.target[0], units),
SpeckleObjects::Geometry.length_to_speckle(camera.target[1], units),
SpeckleObjects::Geometry.length_to_speckle(camera.target[2], units),
units
)
end
def self.get_camera_origin(camera, units)
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
end
@@ -5,7 +5,8 @@ require_relative 'point'
require_relative 'bounding_box'
require_relative '../base'
require_relative '../primitive/interval'
require_relative '../../sketchup_model/dictionary/dictionary_handler'
require_relative '../../sketchup_model/dictionary/base_dictionary_handler'
require_relative '../../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module SpeckleObjects
@@ -17,11 +18,11 @@ module SpeckleConnector
# @param start_pt [Geometry::Point] start point speckle object of the speckle line.
# @param end_pt [Geometry::Point] end point speckle object of the speckle line.
# @param domain [Primitive::Interval] interval speckle object of the speckle line -represents domain.
# @param bbox [Geometry::BoundingBox] bounding box speckle object of the speckle line.
# @param units [String] units of the speckle line.
# @param application_id [String, nil] entity id of the {Sketchup::Edge} that represents to the speckle line.
# rubocop:disable Metrics/ParameterLists
def initialize(start_pt:, end_pt:, domain:, bbox:, units:, sketchup_attributes: {}, application_id: nil)
def initialize(start_pt:, end_pt:, domain:, units:, layer:,
sketchup_attributes: {}, speckle_schema: {}, application_id: nil)
super(
speckle_type: 'Objects.Geometry.Line',
total_children_count: 0,
@@ -31,39 +32,40 @@ module SpeckleConnector
self[:start] = start_pt
self[:end] = end_pt
self[:domain] = domain
self[:bbox] = bbox
self[:units] = units
self[:layer] = layer unless layer.nil?
self[:SpeckleSchema] = speckle_schema if speckle_schema.any?
self[:sketchup_attributes] = sketchup_attributes if sketchup_attributes.any?
end
# rubocop:enable Metrics/ParameterLists
# @param edge [Sketchup::Edge] edge to convert line.
def self.from_edge(edge, units, model_preferences)
dictionaries = {}
if model_preferences[:include_entity_attributes]
dictionaries = SketchupModel::Dictionary::DictionaryHandler.attribute_dictionaries_to_speckle(edge)
end
dictionaries = SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_speckle(edge, model_preferences)
att = dictionaries.any? ? { dictionaries: dictionaries } : {}
speckle_schema = SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.speckle_schema_to_speckle(edge)
start_pt = Geometry::Point.from_vertex(edge.start.position, units)
end_pt = Geometry::Point.from_vertex(edge.end.position, units)
domain = Primitive::Interval.from_numeric(0, Float(edge.length), units)
bbox = Geometry::BoundingBox.from_bounds(edge.bounds, units)
Line.new(
start_pt: start_pt,
end_pt: end_pt,
domain: domain,
bbox: bbox,
units: units,
layer: edge.layer.display_name,
sketchup_attributes: att,
speckle_schema: speckle_schema,
application_id: edge.persistent_id.to_s
)
end
# @param _state [States::State] state of the application.
# @param line [Object] object represents Speckle line.
# @param layer [Sketchup::Layer] layer to add {Sketchup::Edge} into it.
# @param entities [Sketchup::Entities] entities collection to add {Sketchup::Edge} into it.
# rubocop:disable Metrics/AbcSize
def self.to_native(line, layer, entities)
def self.to_native(state, line, entities, &_convert_to_native)
if line.key?('value')
values = line['value']
points = values.each_slice(3).to_a.map { |pt| Point.to_native(pt[0], pt[1], pt[2], line['units']) }
@@ -74,24 +76,24 @@ module SpeckleConnector
end_pt = Point.to_native(line['end']['x'], line['end']['y'], line['end']['z'], line['units'])
edges = entities.add_edges(start_pt, end_pt)
end
line_layer = state.sketchup_state.sketchup_model.layers.to_a.find { |l| l.display_name == line['layer'] }
edges.each do |edge|
edge.layer = layer
edge.layer = line_layer unless line_layer.nil?
unless line['sketchup_attributes'].nil?
SketchupModel::Dictionary::DictionaryHandler
SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_native(edge, line['sketchup_attributes']['dictionaries'])
end
end
return state, edges
end
# rubocop:enable Metrics/AbcSize
def self.test_line(start_point, end_point, units)
domain = Primitive::Interval.from_numeric(0, 5, units)
bbox = Geometry::BoundingBox.test_bounds(units)
Line.new(
start_pt: start_point,
end_pt: end_point,
domain: domain,
bbox: bbox,
application_id: '',
units: units
)
@@ -3,8 +3,10 @@
require_relative '../base'
require_relative '../geometry/bounding_box'
require_relative '../other/render_material'
require_relative '../../sketchup_model/query/entity'
require_relative '../../convertors/clean_up'
require_relative '../../sketchup_model/dictionary/dictionary_handler'
require_relative '../../sketchup_model/dictionary/base_dictionary_handler'
require_relative '../../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module SpeckleObjects
@@ -25,27 +27,28 @@ 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:, layer:, speckle_schema: {}, application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: nil,
application_id: application_id,
id: nil
)
@vertices = []
@polygons = []
@units = units
self[:units] = units
self[:layer] = layer
self[:renderMaterial] = render_material
self[:bbox] = bbox
self[:'@(31250)vertices'] = vertices
self[:'@(62500)faces'] = faces
self[:sketchup_attributes] = sketchup_attributes if sketchup_attributes.any?
self[:SpeckleSchema] = speckle_schema if speckle_schema.any?
end
# rubocop:enable Metrics/ParameterLists
@@ -53,9 +56,11 @@ module SpeckleConnector
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/CyclomaticComplexity
def self.to_native(sketchup_model, mesh, layer, entities, model_preferences)
# rubocop:disable Metrics/PerceivedComplexity:
def self.to_native(state, mesh, entities, &convert_to_native)
model_preferences = state.user_state.preferences[:model]
# Get soft? flag of {Sketchup::Edge} object to understand smoothness of edge.
is_soften = get_soften_setting(mesh)
is_soften = get_soften_setting(mesh, entities)
smooth_flags = is_soften ? 4 : 1
# Get native points to add polygon into native mesh.
points = get_native_points(mesh)
@@ -69,54 +74,79 @@ module SpeckleConnector
indices = faces.shift(num_pts)
native_mesh.add_polygon(indices.map { |index| points[index] })
end
material = Other::RenderMaterial.to_native(sketchup_model, mesh['renderMaterial'])
entities.add_faces_from_mesh(native_mesh, smooth_flags, material)
state, _materials = Other::RenderMaterial.to_native(state, mesh['renderMaterial'],
entities, &convert_to_native)
# Find and assign material if exist
unless mesh['renderMaterial'].nil?
material_name = mesh['renderMaterial']['name'] || mesh['renderMaterial']['id']
# Retrieve material from state
material = state.sketchup_state.materials.by_id(material_name)
end
# Add faces from mesh with material and smooth setting
entities.add_faces_from_mesh(native_mesh, smooth_flags, material, material)
added_faces = entities.grep(Sketchup::Face).last(native_mesh.polygons.length)
mesh_layer = state.sketchup_state.sketchup_model.layers.to_a.find { |l| l.display_name == mesh['layer'] }
added_faces.each do |face|
face.layer = layer
face.layer = mesh_layer unless mesh_layer.nil?
unless mesh['sketchup_attributes'].nil?
SketchupModel::Dictionary::DictionaryHandler
SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_native(face, mesh['sketchup_attributes']['dictionaries'])
end
end
# Merge only added faces in this scope
Converters::CleanUp.merge_coplanar_faces(added_faces) if model_preferences[:merge_coplanar_faces]
native_mesh
if model_preferences[:merge_coplanar_faces]
added_faces = Converters::CleanUp.merge_coplanar_faces(added_faces)
end
return state, added_faces
end
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity:
# @param face [Sketchup::Face] face to convert mesh
# @param units [String] model units to send Speckle.
# @param model_preferences [Hash{Symbol=>Boolean}] model preferences to check include attributes or not.
# @param global_transform [Geom::Transformation, nil] global transformation value of face if it is not included
# into any component.
# rubocop:disable Style/MultilineTernaryOperator
def self.from_face(face, units, model_preferences)
dictionaries = {}
if model_preferences[:include_entity_attributes]
dictionaries = SketchupModel::Dictionary::DictionaryHandler.attribute_dictionaries_to_speckle(face)
end
def self.from_face(face:, units:, model_preferences:, global_transform: nil, parent_material: nil)
dictionaries = SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_speckle(face, model_preferences)
has_any_soften_edge = face.edges.any?(&:soft?)
att = dictionaries.any? ? { is_soften: has_any_soften_edge, dictionaries: dictionaries }
: { is_soften: has_any_soften_edge }
speckle_schema = SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.speckle_schema_to_speckle(face)
material = face.material || face.back_material || parent_material
speckle_mesh = Mesh.new(
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
render_material: material.nil? ? nil : Other::RenderMaterial.from_material(material),
vertices: [],
faces: [],
sketchup_attributes: att,
layer: face.layer.display_name,
speckle_schema: speckle_schema,
application_id: face.persistent_id
)
speckle_mesh.face_to_mesh(face)
speckle_mesh.face_to_mesh(face, global_transform)
speckle_mesh.update_mesh
speckle_mesh
end
# rubocop:enable Style/MultilineTernaryOperator
def face_to_mesh(face)
# @param global_transform [Geom::Transformation, nil] global transformation value of face if it is not included
# into any component. So it's mesh will be transformed into global coordinates to represent it correctly in
# Speckle viewer or other connectors.
def face_to_mesh(face, global_transform = nil)
mesh = face.loops.count > 1 ? face.mesh : nil
mesh.nil? ? face_vertices_to_array(face) : mesh_points_to_array(mesh)
mesh.nil? ? face_indices_to_array(face) : mesh_faces_to_array(mesh)
if global_transform.nil?
mesh.nil? ? face_vertices_to_array(face) : mesh_points_to_array(mesh)
mesh.nil? ? face_indices_to_array(face) : mesh_faces_to_array(mesh)
else
mesh_points_to_array(face.mesh, global_transform)
mesh_faces_to_array(face.mesh, global_transform)
end
end
# Collects indexed Sketchup vertices into flat array for Speckle use.
@@ -161,7 +191,8 @@ module SpeckleConnector
# Get a flat array of vertices from a sketchup polygon mesh
# @param mesh [Geom::PolygonMesh] mesh to get points.
def mesh_points_to_array(mesh)
def mesh_points_to_array(mesh, global_transform = nil)
mesh.transform!(global_transform) unless global_transform.nil?
mesh.points.each do |pt|
# FIXME: Enable previous line when viewer supports shared vertices
# vertices.push(pt) unless vertices.any? { |point| point == pt }
@@ -171,7 +202,8 @@ module SpeckleConnector
# Get an array of face indices from a sketchup polygon mesh
# @param mesh [Geom::PolygonMesh] mesh to convert into polygons.
def mesh_faces_to_array(mesh)
def mesh_faces_to_array(mesh, global_transform = nil)
mesh.transform!(global_transform) unless global_transform.nil?
mesh.polygons.each do |poly|
global_polygon_array = [poly.count]
poly.each do |index|
@@ -184,12 +216,23 @@ module SpeckleConnector
end
end
def self.get_soften_setting(mesh)
if mesh['sketchup_attributes'].nil?
true
else
mesh['sketchup_attributes']['is_soften'].nil? ? true : mesh['sketchup_attributes']['is_soften']
DEFINITIONS_WILL_BE_HARD_EDGE = %w[
Walls
Floors
Stairs
Structural Foundations
Doors
Windows
].freeze
# @param mesh [Object] speckle mesh object
# @param entities [Sketchup::Entities] sketchup entities that mesh will be created in it as face.
def self.get_soften_setting(mesh, entities)
unless mesh['sketchup_attributes'].nil?
return mesh['sketchup_attributes']['is_soften'].nil? ? true : mesh['sketchup_attributes']['is_soften']
end
return DEFINITIONS_WILL_BE_HARD_EDGE.none? { |def_name| entities.parent.name.include?(def_name) }
end
def self.get_native_points(mesh)
@@ -199,6 +242,39 @@ module SpeckleConnector
end
points
end
# 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, parent_material = nil)
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 || parent_material
layer_name = face.layer.display_name
return layer_name if material.nil?
return material.entityID.to_s + layer_name
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
@@ -2,7 +2,6 @@
require_relative 'length'
require_relative '../base'
require_relative '../../typescript/typescript_object'
module SpeckleConnector
module SpeckleObjects
@@ -0,0 +1,88 @@
# frozen_string_literal: true
require_relative '../base'
require_relative '../other/transform'
require_relative '../other/block_definition'
require_relative '../other/block_instance'
require_relative '../../constants/type_constants'
require_relative '../../sketchup_model/dictionary/dictionary_handler'
module SpeckleConnector
module SpeckleObjects
module GIS
# BoundingBox object definition for Speckle.
class PolygonElement < Base
SPECKLE_TYPE = OBJECTS_GIS_POLYGONELEMENT
def self.get_definition_name(obj, attributes)
return obj['name'] unless obj['name'].nil?
return attributes['name'] unless attributes['name'].nil?
return "def::#{obj['id']}"
end
def self.get_qgis_attributes(obj)
attributes = obj['attributes'].to_h
speckle_properties = %w[id speckle_type totalChildrenCount units applicationId]
speckle_properties.each { |key| attributes.delete(key) }
attributes
end
# Handles polygon element differently from display value.
def self.to_native(state, obj, entities, &convert_to_native)
attributes = get_qgis_attributes(obj)
obj = collect_definition_geometries(obj)
obj['name'] = get_definition_name(obj, attributes)
state, _definitions = Other::BlockDefinition.to_native(
state,
obj,
entities,
&convert_to_native
)
definition = state.sketchup_state.sketchup_model
.definitions[Other::BlockDefinition.get_definition_name(obj)]
Other::BlockInstance.find_and_erase_existing_instance(definition, obj['id'], obj['applicationId'])
t_arr = obj['transform']
transform = t_arr.nil? ? Geom::Transformation.new : Other::Transform.to_native(t_arr, obj['units'])
instance = entities.add_instance(definition, transform)
instance.name = obj['name'] unless obj['name'].nil?
SketchupModel::Dictionary::DictionaryHandler.set_hash(instance, attributes, 'qgis')
SketchupModel::Dictionary::DictionaryHandler.set_hash(definition, attributes, 'qgis')
# Align instance axes that created from display value. (without any transform)
Other::BlockInstance.align_instance_axes(instance)
return state, [instance, definition]
end
def self.collect_definition_geometries(obj)
geometries = []
# FIXME: This type check needed because of QGIS. It can send geometries both way, object or array..
# This is something need to be fixed by QGIS.
if obj['geometry'].is_a?(Array)
obj['geometry'].each do |geometry|
display_value = geometry['displayValue']
geometries += display_value
end
else
geometries += obj['geometry']['displayValue']
end
geometries.each do |geo|
if geo['speckle_type'] && geo['speckle_type'] == OBJECTS_GEOMETRY_MESH
geo['sketchup_attributes'] = { 'is_soften' => false }
end
end
obj['geometry'] = geometries
obj
end
end
end
end
end
@@ -7,7 +7,8 @@ require_relative '../base'
require_relative '../geometry/point'
require_relative '../geometry/mesh'
require_relative '../geometry/bounding_box'
require_relative '../../sketchup_model/dictionary/dictionary_handler'
require_relative '../../sketchup_model/dictionary/base_dictionary_handler'
require_relative '../../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module SpeckleObjects
@@ -17,13 +18,12 @@ module SpeckleConnector
SPECKLE_TYPE = 'Objects.Other.BlockDefinition'
# @param geometry [Object] geometric definition of the block.
# @param base_point [Geometry::Point] base point of the block definition.
# @param name [String] name of the block definition.
# @param units [String] units of the block definition.
# @param application_id [String, NilClass] application id of the block definition.
# rubocop:disable Metrics/ParameterLists
def initialize(geometry:, base_point:, name:, units:, always_face_camera:, sketchup_attributes: {},
application_id: nil)
def initialize(geometry:, name:, units:, always_face_camera:, sketchup_attributes: {},
speckle_schema: {}, application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
@@ -32,11 +32,11 @@ 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[:SpeckleSchema] = speckle_schema if speckle_schema.any?
# '@@' means that it is a detached property.
self['@@geometry'] = geometry
end
# rubocop:enable Metrics/ParameterLists
@@ -44,108 +44,153 @@ module SpeckleConnector
# instance
# @param units [String] units of the Sketchup model
# @param definitions [Hash{String=>BlockDefinition}] all converted {BlockDefinition}s on the converter.
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/MethodLength
def self.from_definition(definition, units, definitions, preferences, &convert)
guid = definition.guid
return definitions[guid] if definitions.key?(guid)
dictionaries = {}
if preferences[:model][:include_entity_attributes]
dictionaries = SketchupModel::Dictionary::DictionaryHandler.attribute_dictionaries_to_speckle(definition)
end
# rubocop:disable Metrics/ParameterLists
def self.from_definition(definition, units, preferences, speckle_state, parent, &convert)
dictionaries = SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_speckle(definition, preferences[:model])
att = dictionaries.any? ? { dictionaries: dictionaries } : {}
speckle_schema = SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler
.speckle_schema_to_speckle(definition)
# 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.entities, 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
speckle_schema: speckle_schema,
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/ParameterLists
def self.get_definition_name(def_obj)
return def_obj['name'] unless def_obj['name'].nil?
return "def::#{def_obj['applicationId']}"
end
# Finds or creates a component definition from the geometry and the given name
# @param sketchup_model [Sketchup::Model] sketchup model to check block definitions.
# @param state [States::State] state of the application.
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/ParameterLists
def self.to_native(sketchup_model, geometry, layer, name, always_face_camera, model_preferences,
sketchup_attributes, application_id = '', &convert)
definition = sketchup_model.definitions[name]
return definition if definition && (definition.name == name || definition.guid == application_id)
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
def self.to_native(state, definition_obj, _entities, &convert_to_native)
sketchup_model = state.sketchup_state.sketchup_model
definition&.entities&.clear!
definition ||= sketchup_model.definitions.add(name)
definition.layer = layer
if geometry.is_a?(Array)
geometry.each { |obj| convert.call(obj, layer, model_preferences, definition.entities) }
# FIXME: Check later this is a valid check or not. Maybe unnecessary? If necessary document it!
# Check definitions from sketchup_model with name and application id
definition_name = get_definition_name(definition_obj)
application_id = definition_obj['applicationId']
definition = sketchup_model.definitions[definition_name]
# Check any entities of definition changed
entities_updated = entities_updated?(definition, definition_obj)
if definition && !entities_updated &&
(definition.name == definition_name || definition.guid == application_id)
return state, [definition]
end
if geometry.is_a?(Hash) && !geometry['speckle_type'].nil?
convert.call(geometry, layer, model_preferences, definition.entities)
geometry = definition_obj['geometry'] || definition_obj['@geometry'] || definition_obj['displayValue']
always_face_camera = definition_obj['always_face_camera'].nil? ? false : definition_obj['always_face_camera']
sketchup_attributes = definition_obj['sketchup_attributes']
definition&.entities&.clear!
definition ||= sketchup_model.definitions.add(definition_name)
if geometry.is_a?(Array)
geometry.each do |obj|
state = convert_to_native.call(state, obj, definition.entities)
end
end
if geometry.is_a?(Hash) && !definition_obj['speckle_type'].nil?
state = convert_to_native.call(state, geometry, definition.entities)
end
# puts("definition finished: #{name} (#{application_id})")
# puts(" entity count: #{definition.entities.count}")
definition.behavior.always_face_camera = always_face_camera
unless sketchup_attributes.nil?
SketchupModel::Dictionary::DictionaryHandler
SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_native(definition, sketchup_attributes['dictionaries'])
end
definition
return state, [definition]
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/ParameterLists
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/AbcSize
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def self.group_entities_to_speckle(definition, units, definitions, preferences, &convert)
orphan_edges = definition.entities.grep(Sketchup::Edge).filter { |edge| edge.faces.none? }
def self.group_entities_to_speckle(entities, preferences, speckle_state, parent, &convert)
orphan_edges = 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)
nested_blocks = entities.grep(Sketchup::ComponentInstance).collect do |component_instance|
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)
nested_groups = entities.grep(Sketchup::Group).collect do |group|
new_speckle_state, converted = convert.call(group, preferences, speckle_state, parent)
speckle_state = new_speckle_state
converted
end
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])
entities.grep(Sketchup::Face).collect do |face|
next unless SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.attribute_dictionary(face).nil?
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 = []
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
@@ -153,26 +198,30 @@ 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 = Geometry::Mesh.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]
has_attribute_dictionary = !(face.attribute_dictionaries.nil? || face.attribute_dictionaries.first.nil?)
return face.persistent_id.to_s if has_attribute_dictionary
# It is important check for hosted elements that wrapped into component in sketchup.
# Their definition name might be stay same but their speckle ids should be checked
# to compare they updated or not.
def self.entities_updated?(definition, speckle_definition)
children_changed = false
unless definition.nil?
# TODO: Here we need to check later if definition invalid or not.
previous_speckle_id = definition.get_attribute(SPECKLE_BASE_OBJECT, 'speckle_id')
children_changed = previous_speckle_id != speckle_definition['id']
end
material = face.material || face.back_material
return 'none' if material.nil?
return material.entityID.to_s
children_changed
end
end
end
@@ -5,7 +5,8 @@ require_relative 'transform'
require_relative 'block_definition'
require_relative '../base'
require_relative '../geometry/bounding_box'
require_relative '../../sketchup_model/dictionary/dictionary_handler'
require_relative '../../sketchup_model/dictionary/base_dictionary_handler'
require_relative '../../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module SpeckleObjects
@@ -23,8 +24,8 @@ module SpeckleConnector
# @param sketchup_attributes [Hash{Symbol=>Object}] sketchup attributes of the block instance.
# @param application_id [String] application id of the block instance.
# rubocop:disable Metrics/ParameterLists
def initialize(units:, is_sketchup_group:, name:, render_material:, transform:, block_definition:,
sketchup_attributes: {}, application_id: nil)
def initialize(units:, is_sketchup_group:, name:, render_material:, transform:, block_definition:, layer:,
sketchup_attributes: {}, speckle_schema: {}, application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
@@ -33,45 +34,59 @@ module SpeckleConnector
)
self[:units] = units
self[:name] = name
self[:layer] = layer
self[:is_sketchup_group] = is_sketchup_group
self[:renderMaterial] = render_material
self[:transform] = transform
self[:sketchup_attributes] = sketchup_attributes if sketchup_attributes.any?
self[:SpeckleSchema] = speckle_schema if speckle_schema.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)
dictionaries = {}
if preferences[:model][:include_entity_attributes]
dictionaries = SketchupModel::Dictionary::DictionaryHandler.attribute_dictionaries_to_speckle(group)
end
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 = SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_speckle(group, preferences[:model])
att = dictionaries.any? ? { dictionaries: dictionaries } : {}
BlockInstance.new(
speckle_schema = SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.speckle_schema_to_speckle(group)
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,
layer: group.layer.display_name,
sketchup_attributes: att,
speckle_schema: speckle_schema,
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)
dictionaries = {}
if preferences[:model][:include_entity_attributes]
dictionaries = SketchupModel::Dictionary::DictionaryHandler
.attribute_dictionaries_to_speckle(component_instance)
end
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 = SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_speckle(component_instance, preferences[:model])
att = dictionaries.any? ? { dictionaries: dictionaries } : {}
BlockInstance.new(
speckle_schema = SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler
.speckle_schema_to_speckle(component_instance)
block_instance = BlockInstance.new(
units: units,
is_sketchup_group: false,
name: component_instance.name == '' ? nil : component_instance.name,
@@ -81,82 +96,132 @@ 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,
layer: component_instance.layer.display_name,
sketchup_attributes: att,
application_id: component_instance.guid
speckle_schema: speckle_schema,
application_id: component_instance.persistent_id.to_s
)
return speckle_state, block_instance
end
# rubocop:enable Metrics/MethodLength
# Creates a component instance from a block
# @param sketchup_model [Sketchup::Model] sketchup model to check block definitions.
# @param state [States::State] state of the application.
# @param block [Object] block object that represents Speckle block.
# @param layer [Sketchup::Layer] layer to add {Sketchup::Edge} into it.
# @param entities [Sketchup::Entities] entities collection to add {Sketchup::Edge} into it.
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/ParameterLists
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def self.to_native(sketchup_model, block, layer, entities, model_preferences, &convert)
def self.to_native(state, block, entities, &convert_to_native)
# is_group = block.key?("is_sketchup_group") && block["is_sketchup_group"]
# something about this conversion is freaking out if nested block geo is a group
# so this is set to false always until I can figure this out
is_group = false
# is_group = block['is_sketchup_group']
block_definition = block['@blockDefinition'] || block['blockDefinition']
geometry = block_definition['@geometry'] || block_definition['geometry']
definition = BlockDefinition.to_native(
sketchup_model,
geometry,
layer,
block_definition['name'],
block_definition['always_face_camera'].nil? ? false : block_definition['always_face_camera'],
model_preferences,
block_definition['sketchup_attributes'],
block_definition['applicationId'],
&convert
# NOTE: nil checks for backward compatibility
block_definition = block['definition'] || block['blockDefinition'] || block['@blockDefinition']
state, _definitions = BlockDefinition.to_native(
state,
block_definition,
entities,
&convert_to_native
)
instance_name = block['name'].nil? || block['name'].empty? ? block['id'] : block['name']
t_arr = block['transform'].is_a?(Hash) ? block['transform']['value'] : block['transform']
definition = state.sketchup_state.sketchup_model
.definitions[BlockDefinition.get_definition_name(block_definition)]
block_layer = state.sketchup_state.sketchup_model.layers.to_a.find { |l| l.display_name == block['layer'] }
return add_instance_from_definition(state, block, block_layer, entities, definition, is_group, &convert_to_native)
end
def self.get_transform_matrix(block)
if block['transform'].is_a?(Hash)
block['transform']['matrix'] || block['transform']['value']
else
block['transform']
end
end
# takes a component definition and finds and erases the first instance with the matching name
# (and optionally the applicationId)
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/CyclomaticComplexity
def self.find_and_erase_existing_instance(definition, upcoming_speckle_id, upcoming_app_id = '')
definition.instances.find do |ins|
next if ins.attribute_dictionaries.nil?
next if ins.attribute_dictionaries.to_a.empty?
next if ins.attribute_dictionaries.to_a.none? { |dict| dict.name == SPECKLE_BASE_OBJECT }
dict = ins.attribute_dictionaries.to_a.find { |d| d.name == SPECKLE_BASE_OBJECT }
speckle_id = dict[:speckle_id]
application_id = dict[:application_id]
speckle_id == upcoming_speckle_id || application_id == upcoming_app_id
end&.erase!
end
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/ParameterLists
def self.add_instance_from_definition(state, block, layer, entities, definition, is_group, &convert_to_native)
t_arr = get_transform_matrix(block)
transform = Other::Transform.to_native(t_arr, block['units'])
instance = if is_group
# rubocop:disable SketchupSuggestions/AddGroup
group = entities.add_group(definition.entities.to_a)
group.layer = layer
group.layer = layer unless layer.nil?
group
# rubocop:enable SketchupSuggestions/AddGroup
else
instance = entities.add_instance(definition, transform)
instance.layer = layer
instance.layer = layer unless layer.nil?
instance
end
# erase existing instances after creation and before rename because you can't have definitions
# without instances
find_and_erase_existing_instance(definition, instance_name, block['applicationId'])
find_and_erase_existing_instance(definition, block['id'], block['applicationId'])
puts("Failed to create instance for speckle block instance #{block['id']}") if instance.nil?
# Transform already applied to instance unless is group
instance.transformation = transform if is_group
instance.material = Other::RenderMaterial.to_native(sketchup_model, block['renderMaterial'])
instance.name = instance_name
state, _materials = Other::RenderMaterial.to_native(state, block['renderMaterial'],
entities, &convert_to_native)
# Retrieve material from state
unless block['renderMaterial'].nil?
material_name = block['renderMaterial']['name'] || block['renderMaterial']['id']
material = state.sketchup_state.materials.by_id(material_name)
instance.material = material
end
instance.name = block['name'] unless block['name'].nil?
unless block['sketchup_attributes'].nil?
SketchupModel::Dictionary::DictionaryHandler
SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_native(instance, block['sketchup_attributes']['dictionaries'])
end
instance
return state, [instance, definition]
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/ParameterLists
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/ParameterLists
# takes a component definition and finds and erases the first instance with the matching name
# (and optionally the applicationId)
def self.find_and_erase_existing_instance(definition, name, app_id = '')
definition.instances.find { |ins| ins.name == name || ins.guid == app_id }&.erase!
# Instances that created from display value that has no any transform value.
# Because of this reason their definition created with origin axis. We basically create transformation
# vector between bounds min to origin, to move definition axis to bounds min. Otherwise they looks weird in
# sketchup and might be cumbersome when we want to add new entities into definition.
# @param instance [Sketchup::ComponentInstance] instance to align axis to it's bounds
def self.align_instance_axes(instance)
bounds = instance.bounds
transform = Geom::Transformation.translation(bounds.min.vector_to(Geom::Point3d.new(0, 0, 0)))
entities = instance.definition.entities
entities.transform_entities(transform, entities.to_a)
instance_transform = instance.transformation
instance.transform!(instance_transform * transform.inverse * instance_transform.inverse)
end
end
end
@@ -0,0 +1,25 @@
# frozen_string_literal: true
module SpeckleConnector
module SpeckleObjects
module Others
# Color object transformations between sketchup and speckle.
class Color
# @param color [Sketchup::Color] color to convert speckle object
def self.to_speckle(color)
{
red: color.red,
green: color.green,
blue: color.blue,
alpha: color.alpha
}
end
def self.to_native(speckle_color)
Sketchup::Color.new(speckle_color['red'], speckle_color['green'],
speckle_color['blue'], speckle_color['alpha'])
end
end
end
end
end
@@ -0,0 +1,88 @@
# frozen_string_literal: true
require_relative 'render_material'
require_relative 'transform'
require_relative 'block_definition'
require_relative '../../immutable/immutable'
require_relative '../../ext/immutable_ruby/hash'
require_relative '../base'
require_relative '../geometry/bounding_box'
require_relative '../../sketchup_model/dictionary/base_dictionary_handler'
module SpeckleConnector
module SpeckleObjects
module Other
# DisplayValue object definition for Speckle that represents as BlockInstance in Sketchup.
class DisplayValue
def self.get_definition_name(def_obj)
family = def_obj['family']
type = def_obj['type']
category = def_obj['category']
element_id = def_obj['elementId']
return format_naming_convention([family, type, category, element_id]) unless element_id.nil?
return "def::#{def_obj['applicationId']}" unless def_obj['applicationId'].nil?
return "def::#{def_obj['id']}"
end
def self.format_naming_convention(entries)
name = ''
entries.each_with_index do |entry, index|
next if entry.nil?
name += if index == entries.length - 1
entry.to_s
else
"#{entry}-"
end
end
name
end
# Creates a component definition and instance from a speckle object with a display value
# @param state [States::State] state of the application.
def self.to_native(state, obj, entities, &convert_to_native)
# Switch displayValue with geometry
obj = collect_definition_geometries(obj)
obj['name'] = get_definition_name(obj)
state, _definitions = BlockDefinition.to_native(
state,
obj,
entities,
&convert_to_native
)
definition = state.sketchup_state.sketchup_model.definitions[BlockDefinition.get_definition_name(obj)]
BlockInstance.find_and_erase_existing_instance(definition, obj['id'], obj['applicationId'])
t_arr = obj['transform']
transform = t_arr.nil? ? Geom::Transformation.new : OTHER::Transform.to_native(t_arr, obj['units'])
instance = entities.add_instance(definition, transform)
instance.name = obj['name'] unless obj['name'].nil?
# Align instance axes that created from display value. (without any transform)
# BlockInstance.align_instance_axes(instance)
return state, [instance, definition]
end
def self.collect_definition_geometries(obj)
obj['geometry'] = obj['displayValue']
if !obj['elements'].nil? && obj['elements'].is_a?(Array)
obj['elements'].each do |element|
# Mullions is a special case here, they are extracted as base object with @displayValue from revit..
if element['@displayValue'].nil?
obj['geometry'].append(element)
else
obj['geometry'] += element['@displayValue']
end
end
end
obj
end
end
end
end
end
@@ -1,7 +1,6 @@
# frozen_string_literal: true
require_relative '../base'
require_relative '../../typescript/typescript_object'
module SpeckleConnector
module SpeckleObjects
@@ -50,21 +49,25 @@ module SpeckleConnector
)
end
# @param sketchup_model [Sketchup::Model] active model on the sketchup.
def self.to_native(sketchup_model, render_material)
return if render_material.nil?
# @param state [States::State] state of the application.
def self.to_native(state, render_material, _entities, &_convert_to_native)
return state, [] if render_material.nil?
sketchup_model = state.sketchup_state.sketchup_model
materials = state.sketchup_state.materials
# return material with same name if it exists
name = render_material['name'] || render_material['id']
material = sketchup_model.materials[name]
return material if material
material = materials.by_id(name)
return state, [material] if material
# create a new sketchup material
material = sketchup_model.materials.add(name)
material.alpha = render_material['opacity']
argb = render_material['diffuse']
material.color = Sketchup::Color.new((argb >> 16) & 255, (argb >> 8) & 255, argb & 255, (argb >> 24) & 255)
material
new_sketchup_state = state.sketchup_state.with_materials(materials.add_material(name, material))
return state.with_sketchup_state(new_sketchup_state), [material]
end
end
end
@@ -0,0 +1,24 @@
# frozen_string_literal: true
require_relative 'color'
module SpeckleConnector
module SpeckleObjects
module Others
# Rendering options for scenes.
class RenderingOptions
# @param options [Sketchup::RenderingOptions] rendering options to convert speckle object
def self.to_speckle(options)
options.to_h.collect do |option_prop, option_value|
speckle_value = if option_value.is_a?(Sketchup::Color)
Color.to_speckle(option_value)
else
option_value
end
[option_prop.to_sym, speckle_value]
end.to_h
end
end
end
end
end
@@ -11,8 +11,7 @@ module SpeckleConnector
# @param units [String] units of the transform.
# @param value [Array<Numeric>] values of the transform.
# @param sketchup_attributes [Other::BlockDefinition] sketchup attributes of the transform.
def initialize(units:, value:, sketchup_attributes: {})
def initialize(units:, value:)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
@@ -21,7 +20,6 @@ module SpeckleConnector
)
self[:units] = units
self[:value] = value
self[:sketchup_attributes] = sketchup_attributes
end
def self.from_transformation(transformation, units)
@@ -0,0 +1,79 @@
# frozen_string_literal: true
require_relative '../base'
require_relative '../other/color'
module SpeckleConnector
module SpeckleObjects
module Relations
# Sketchup layer (tag) tree relation.
class Layer < Base
SPECKLE_TYPE = 'Speckle.Core.Models.Collection'
def initialize(name:, visible:, is_folder:, color: nil, layers_and_folders: [], application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: application_id,
id: nil
)
self[:name] = name
self[:color] = color
self[:visible] = visible
self[:is_folder] = is_folder
self[:collectionType] = 'layer'
self[:elements] = layers_and_folders if layers_and_folders.any?
end
# @param speckle_layer [Object] speckle layer object.
# @param folder [Sketchup::Layers, Sketchup::LayerFolder] folder to create layers in it.
def self.to_native_layer(speckle_layer, folder, sketchup_model)
layer = sketchup_model.layers.add_layer(speckle_layer['name'])
layer.visible = speckle_layer['visible'] unless speckle_layer['visible'].nil?
layer.color = SpeckleObjects::Others::Color.to_native(speckle_layer['color']) if speckle_layer['color']
folder.add_layer(layer) if folder.is_a?(Sketchup::LayerFolder)
end
def self.to_native_layer_folder(speckle_layer_folder, folder, sketchup_model)
speckle_layers = speckle_layer_folder['elements'].select { |layer_or_fol| layer_or_fol['elements'].nil? }
speckle_layers.each do |speckle_layer|
to_native_layer(speckle_layer, folder, sketchup_model)
end
speckle_folders = speckle_layer_folder['elements'].reject { |layer_or_fol| layer_or_fol['elements'].nil? }
speckle_folders.each do |speckle_folder|
sub_folder = folder.add_folder(speckle_folder['name'])
sub_folder.visible = speckle_folder['visible'] unless speckle_folder['visible'].nil?
to_native_layer_folder(speckle_folder, sub_folder, sketchup_model)
end
end
# @param folder [Sketchup::LayerFolder] sketchup layer folder that might contains other folders and layers.
def self.from_folder(folder)
layers = folder.layers.collect { |layer| from_layer(layer) }
sub_folders = folder.folders.collect { |sub_folder| from_folder(sub_folder) }
Layer.new(
name: folder.display_name,
visible: folder.visible?,
is_folder: true,
layers_and_folders: layers + sub_folders,
application_id: folder.persistent_id
)
end
# @param layer [Sketchup::Layer] sketchup layer (tag) that objects can be assigned..
def self.from_layer(layer)
Layer.new(
name: layer.display_name,
visible: layer.visible?,
is_folder: false,
color: SpeckleObjects::Others::Color.to_speckle(layer.color),
application_id: layer.persistent_id
)
end
end
end
end
end
@@ -0,0 +1,52 @@
# frozen_string_literal: true
require_relative 'layer'
require_relative '../base'
module SpeckleConnector
module SpeckleObjects
module Relations
# Sketchup layers (tag) tree relation. The difference between Layer is, this is the top object that holds
# properties for all layers or folders, i.e. active layer.
class Layers < Base
SPECKLE_TYPE = 'Speckle.Core.Models.Collection'
def initialize(active:, layers:)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: nil,
id: nil
)
self[:active_layer] = active
self[:elements] = layers
end
def self.to_native(layers_relation, sketchup_model)
folder = sketchup_model.layers
SpeckleObjects::Relations::Layer.to_native_layer_folder(layers_relation, folder, sketchup_model)
active_layer = folder.to_a.find { |layer| layer.display_name == layers_relation['active_layer'] }
sketchup_model.active_layer = active_layer unless active_layer.nil?
end
def self.from_model(sketchup_model)
# init with headless layers
headless_layers = sketchup_model.layers.layers.collect do |layer|
SpeckleObjects::Relations::Layer.from_layer(layer)
end
folders = sketchup_model.layers.folders.collect do |folder|
SpeckleObjects::Relations::Layer.from_folder(folder)
end
Layers.new(
active: sketchup_model.active_layer.display_name,
layers: headless_layers + folders
)
end
end
end
end
end
@@ -0,0 +1,32 @@
# frozen_string_literal: true
require_relative '../other/block_definition'
require_relative '../base'
module SpeckleConnector
module SpeckleObjects
module Revit
module Other
# RevitDefinition for Speckle.
class RevitDefinition < Base
SPECKLE_TYPE = OBJECTS_OTHER_REVIT_REVITINSTANCE
def self.get_definition_name(def_obj)
family = def_obj['family']
type = def_obj['type']
category = def_obj['category']
return "#{family}-#{type}-#{category}-#{def_obj['elementId']}"
end
def self.to_native(state, definition, entities, &convert_to_native)
definition_name = get_definition_name(definition)
definition['name'] = definition_name
definition['displayValue'] += definition['elements'] unless definition['elements'].nil?
SpeckleObjects::Other::BlockDefinition.to_native(state, definition, entities, &convert_to_native)
end
end
end
end
end
end
@@ -0,0 +1,42 @@
# frozen_string_literal: true
require_relative 'revit_definition'
require_relative '../base'
module SpeckleConnector
module SpeckleObjects
module Revit
module Other
# RevitInstance object definition for Speckle.
class RevitInstance < Base
SPECKLE_TYPE = OBJECTS_OTHER_REVIT_REVITINSTANCE
# Creates a component instance from a block
# @param state [States::State] state of the application.
# @param block [Object] block object that represents Speckle block.
# @param layer [Sketchup::Layer] layer to add {Sketchup::Edge} into it.
# @param entities [Sketchup::Entities] entities collection to add {Sketchup::Edge} into it.
def self.to_native(state, block, entities, &convert_to_native)
block_definition = block['definition']
state, _definitions = RevitDefinition.to_native(
state,
block_definition,
entities,
&convert_to_native
)
definition = state.sketchup_state.sketchup_model
.definitions[RevitDefinition.get_definition_name(block_definition)]
layer = state.sketchup_state.sketchup_model.layers.to_a.find { |l| l.display_name == block['category'] }
return SpeckleObjects::Other::BlockInstance.add_instance_from_definition(
state, block, layer, entities, definition, false, &convert_to_native
)
end
end
end
end
end
end
@@ -0,0 +1,35 @@
# frozen_string_literal: true
require_relative '../../../base'
module SpeckleConnector
module SpeckleObjects
module Speckle
module Core
module Models
# Collection object that collect other speckle objects under it's elements.
class Collection < Base
SPECKLE_TYPE = 'Speckle.Core.Models.Collection'
# @param name [String] name of the collection.
# @param collection_type [String] type of the collection like, layers, categories etc..
# @param elements [Array<Object>] elements of the collection.
# @param application_id [String, nil] id of the collection on the model.
def initialize(name:, collection_type:, elements: [], application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: application_id,
id: nil
)
self[:name] = name
self[:collectionType] = collection_type
self[:elements] = elements
end
end
end
end
end
end
end
@@ -0,0 +1,96 @@
# frozen_string_literal: true
require_relative 'collection'
require_relative '../../../../sketchup_model/query/layer'
require_relative '../../../other/color'
module SpeckleConnector
module SpeckleObjects
module Speckle
module Core
module Models
# LayerCollection object that collect other speckle objects under it's elements.
class LayerCollection < Collection
SPECKLE_TYPE = 'Speckle.Core.Models.Collection'
def initialize(name:, visible:, is_folder:, color: nil, elements: [], application_id: nil)
super(
name: name,
collection_type: 'layer',
elements: elements,
application_id: application_id
)
self[:visible] = visible
self[:is_folder] = is_folder
self[:color] = color unless color.nil?
end
def self.create_layer_collections(sketchup_model)
headless_layers = sketchup_model.layers.layers.collect do |layer|
from_layer(layer)
end
folders = sketchup_model.layers.folders.collect do |folder|
from_folder(folder)
end
headless_layers + folders
end
# @param folder [Sketchup::LayerFolder] sketchup layer folder that might contains other folders and layers.
def self.from_folder(folder)
layers = folder.layers.collect { |layer| from_layer(layer) }
sub_folders = folder.folders.collect { |sub_folder| from_folder(sub_folder) }
LayerCollection.new(
name: folder.display_name,
visible: folder.visible?,
is_folder: true,
elements: layers + sub_folders,
application_id: folder.persistent_id
)
end
# @param layer [Sketchup::Layer] sketchup layer (tag) that objects can be assigned..
def self.from_layer(layer)
LayerCollection.new(
name: layer.display_name,
visible: layer.visible?,
is_folder: false,
color: SpeckleObjects::Others::Color.to_speckle(layer.color),
application_id: layer.persistent_id
)
end
# @param entity_layer [Sketchup::Layer] entity layer.
# @param collection [Array] collection to search elements for entity's layer.
def self.get_or_create_layer_collection(entity_layer, collection)
folder_path = SpeckleConnector::SketchupModel::Query::Layer.path(entity_layer)
entity_layer_path = folder_path + [entity_layer]
entity_layer_path.each do |folder|
collection_candidate = collection[:elements].find do |el|
next if el.is_a?(Array)
el[:speckle_type] == SPECKLE_TYPE && el[:collectionType] == 'layer' &&
el[:name] == folder.display_name
end
if collection_candidate.nil?
color = folder.respond_to?(:color) ? SpeckleObjects::Others::Color.to_speckle(folder.color) : nil
collection_candidate = LayerCollection.new(
name: folder.display_name,
visible: folder.visible?,
is_folder: folder.is_a?(Sketchup::LayerFolder),
color: color
)
# Before switching collection with the new one, we should add it to current collection's elements
collection[:elements].append(collection_candidate)
end
collection = collection_candidate
end
collection
end
end
end
end
end
end
end
@@ -0,0 +1,69 @@
# frozen_string_literal: true
require_relative 'collection'
require_relative 'layer_collection'
require_relative '../../../built_elements/view3d'
require_relative '../../../built_elements/revit/direct_shape'
module SpeckleConnector
module SpeckleObjects
module Speckle
module Core
module Models
# ModelCollection object that collect other speckle objects under it's elements.
class ModelCollection < Collection
DIRECT_SHAPE = SpeckleObjects::BuiltElements::Revit::DirectShape
VIEW3D = SpeckleObjects::BuiltElements::View3d
def initialize(name:, active_layer:, elements: [], application_id: nil)
super(
name: name,
collection_type: 'sketchup model',
elements: elements,
application_id: application_id
)
self[:active_layer] = active_layer
end
def self.from_sketchup_model(sketchup_model, speckle_state, units, preferences, &convert)
model_collection = ModelCollection.new(
name: 'Sketchup Model', active_layer: sketchup_model.active_layer.display_name,
application_id: sketchup_model.guid
)
# Direct shapes will pass directly to elements which are already flattened with all children
model_collection[:elements] += collect_direct_shapes(sketchup_model, units, preferences)
# Views will pass directly to elements since they don't have any relation with layers and geometries.
model_collection[:elements] += VIEW3D.from_model(sketchup_model, units) if sketchup_model.pages.any?
# Add layer collections.
model_collection[:elements] += LayerCollection.create_layer_collections(sketchup_model)
sketchup_model.selection.each do |entity|
layer_collection = LayerCollection.get_or_create_layer_collection(entity.layer, model_collection)
new_speckle_state, converted_object_with_entity = convert.call(entity, preferences, speckle_state)
speckle_state = new_speckle_state
unless converted_object_with_entity.nil?
layer_collection[:elements] = [] if layer_collection[:elements].nil?
layer_collection[:elements].append(converted_object_with_entity)
end
end
return speckle_state, model_collection
end
def self.collect_direct_shapes(sketchup_model, units, preferences)
DIRECT_SHAPE.direct_shapes_on_selection(sketchup_model).collect do |entities|
entity = entities[0]
path = entities[1..-1]
direct_shape = DIRECT_SHAPE.from_entity(entity, path, units, preferences)
[direct_shape, [entity]]
end
end
end
end
end
end
end
end
@@ -1,6 +1,8 @@
# frozen_string_literal: true
require_relative '../immutable/immutable'
require_relative '../sketchup_model/materials/materials'
require_relative '../sketchup_model/definitions/definitions'
module SpeckleConnector
module States
@@ -10,9 +12,17 @@ module SpeckleConnector
# @return [Sketchup::Model] active model on the sketchup
attr_reader :sketchup_model
# @return [SketchupModel::Materials] materials by their id
attr_reader :materials
# @return [SketchupModel::Definitions] definitions by their guid and name
attr_reader :definitions
# @param sketchup_model [Sketchup::Model] active model on the sketchup
def initialize(sketchup_model)
@sketchup_model = sketchup_model
@materials = SketchupModel::Materials.from_sketchup_model(sketchup_model)
@definitions = SketchupModel::Definitions.from_sketchup_model(sketchup_model)
end
# @return [Integer] length units code of the sketchup model.
@@ -20,6 +30,10 @@ module SpeckleConnector
def length_units
sketchup_model.options['UnitsOptions']['LengthUnit']
end
def with_materials(new_materials)
with(:@materials => new_materials)
end
end
end
end
+92 -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,47 @@ 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 [ImmutableHash{Integer=>Sketchup::Entity}] persistent_id of the sketchup entity and itself
attr_reader :mapped_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
# TODO: Do cashing later
# @return [ImmutableHash{String=>SpeckleObjects::Other::RenderMaterial}] converted render materials
attr_accessor :render_materials
# TODO: Do cashing later
# @return [ImmutableHash{String=>SpeckleObjects::Other::BlockDefinition}] converted component definitions
attr_accessor :definitions
def initialize(accounts, observers, queue, stream_queue)
@accounts = accounts
@observers = observers
@message_queue = queue
@stream_queue = stream_queue
@speckle_entities = Immutable::EmptyHash
@mapped_entities = Immutable::EmptyHash
@render_materials = Immutable::EmptyHash
@definitions = Immutable::EmptyHash
@relation = Relations::ManyToOneRelation.new
end
# @param callback_name [String] name of the callback command
@@ -33,9 +62,71 @@ module SpeckleConnector
with(:@message_queue => new_queue)
end
def with_mapped_entities_queue(mapped_entities)
new_queue = message_queue.merge({ "mappedEntitiesUpdated":
"mappedEntitiesUpdated(#{JSON.generate(mapped_entities)})" })
with(:@message_queue => new_queue)
end
def with_selection_queue(selection_parameters)
new_queue = message_queue.merge({ "entitySelected":
"entitySelected(#{JSON.generate(selection_parameters)})" })
with(:@message_queue => new_queue)
end
def with_deselection_queue
new_queue = message_queue.merge({ "entitiesDeselected": 'entitiesDeselected()' })
with(:@message_queue => new_queue)
end
def with_invalid_streams_queue
new_queue = message_queue.merge({ "updateInvalidStreams":
"updateInvalidStreams(#{JSON.generate(invalid_streams)})" })
with(:@message_queue => new_queue)
end
def with_empty_invalid_streams_queue
new_queue = message_queue.merge({ "updateInvalidStreams":
"updateInvalidStreams(#{JSON.generate([])})" })
with(:@message_queue => new_queue)
end
def with_accounts(new_accounts)
with(:@accounts => new_accounts)
end
def with_mapped_entity(entity)
new_mapped_entities = mapped_entities.put(entity.persistent_id, entity)
with_mapped_entities(new_mapped_entities)
end
def with_removed_mapped_entity(entity)
new_mapped_entities = mapped_entities.delete(entity.persistent_id)
with_mapped_entities(new_mapped_entities)
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_mapped_entities(new_mapped_entities)
with(:@mapped_entities => new_mapped_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
+18
View File
@@ -36,6 +36,20 @@ module SpeckleConnector
with(:@speckle_state => new_speckle_state)
end
def with_mapped_entities_queue(mapped_entities)
new_speckle_state = speckle_state.with_mapped_entities_queue(mapped_entities)
with(:@speckle_state => new_speckle_state)
end
def with_selection_queue(selection_parameters)
new_speckle_state = if selection_parameters[:selection].any?
speckle_state.with_selection_queue(selection_parameters)
else
speckle_state.with_deselection_queue
end
with(:@speckle_state => new_speckle_state)
end
def with_empty_stream_queue
new_speckle_state = speckle_state.with(:@stream_queue => {})
with(:@speckle_state => new_speckle_state)
@@ -45,6 +59,10 @@ module SpeckleConnector
with(:@speckle_state => new_speckle_state)
end
def with_sketchup_state(new_sketchup_state)
with(:@sketchup_state => new_sketchup_state)
end
def with_user_state(new_user_state)
with(:@user_state => new_user_state)
end
@@ -15,6 +15,14 @@ module SpeckleConnector
@preferences = preferences
end
def user_preferences
@preferences[:user]
end
def model_preferences
@preferences[:model]
end
def with_preferences(new_preferences)
with(:@preferences => new_preferences)
end

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