Compare commits

...

269 Commits

Author SHA1 Message Date
Oğuzhan Koral e49036d8c4 Chore (Attributes): Send and receive ifc classifications 2023-02-20 13:36:52 +03:00
oguzhankoral 4a8356692b Send and receive classifications for component definitions 2023-02-20 13:30:31 +03:00
Oğuzhan Koral adfb7bc63a Fix (BlockDefinition): align detach properties 2023-02-20 12:44:35 +03:00
oguzhankoral 3f41cefa88 Remove basePoint from definition 2023-02-20 12:38:57 +03:00
oguzhankoral 2074ff1987 Rename blockDefinition to definition for BlockInstance 2023-02-20 11:18:50 +03:00
Oğuzhan Koral 65fe189421 Feat (Views): Expand scene support 2023-02-17 22:27:43 +03:00
oguzhankoral bb7590eab4 Simplify sending rendering_options 2023-02-17 22:22:26 +03:00
oguzhankoral 6a04457219 Send and receive rendering options of scenes 2023-02-17 21:51:44 +03:00
oguzhankoral 7f8b1d9586 Include update properties to scenes 2023-02-17 15:39:00 +03:00
Oğuzhan Koral 4362cc53bb Feat (Diffing): MVP for continuous traversal
Fixed issues:
- Origin Point of Components Visible in the Viewer
- Align detach properties with other connectors
- MVP for continuous traversal
- Create SpeckleEntity object as state holder
2023-02-17 02:26:47 +03:00
oguzhankoral 959e6b2a12 Fix rubocop issues 2023-02-17 02:23:27 +03:00
oguzhankoral 2d91070e5a Distint detached properties and dynamically detached properties 2023-02-17 02:17:00 +03:00
oguzhankoral cc038ece32 Remove bbox from mesh 2023-02-17 02:16:59 +03:00
oguzhankoral 7af1292f29 Add setting for diffing 2023-02-17 02:16:59 +03:00
oguzhankoral 2c3fd7a84f Update preferences if empty or has incomplete data 2023-02-17 02:16:59 +03:00
oguzhankoral d36f70dbc7 Align base object serializer with continuous traversal 2023-02-17 02:16:59 +03:00
oguzhankoral 77f5f29c90 Rename batch logging method 2023-02-17 02:16:58 +03:00
oguzhankoral ead45ed843 Remove applicationId from clean base object before traverse props 2023-02-17 02:16:58 +03:00
oguzhankoral a6259bb7eb Print on Speckle folder 2023-02-17 02:16:58 +03:00
oguzhankoral 0249ebeb95 Move is_detached to old order 2023-02-17 02:16:57 +03:00
oguzhankoral 1c3a35a137 Fill delta icon if diffing active on stream 2023-02-17 02:16:57 +03:00
oguzhankoral c12319b417 Activate and deactivate diffing for streams safely 2023-02-17 02:16:57 +03:00
oguzhankoral 90ac00a628 Pass stream if to speckle entities 2023-02-17 02:16:57 +03:00
oguzhankoral e5c71cb3a4 Handle speckle entity dictionaries according validation of object 2023-02-17 02:16:56 +03:00
oguzhankoral ebf0681574 Collect invalid streams from speckle_state 2023-02-17 02:16:56 +03:00
oguzhankoral 642643f412 Remove unnecssary implementation for entity utis 2023-02-17 02:16:56 +03:00
oguzhankoral 8dd65ac255 Init diffing materials with plugin initialization 2023-02-17 02:16:55 +03:00
oguzhankoral 8df58f226b Invalida speckle entities when they edited 2023-02-17 02:16:55 +03:00
oguzhankoral 58b4f2ce14 Disable observers on commands 2023-02-17 02:16:55 +03:00
oguzhankoral 65f0c836fe Align objects with continuous traversal 2023-02-17 02:16:55 +03:00
oguzhankoral 2aa16085a5 Return converted entities regardless 2023-02-17 02:16:54 +03:00
oguzhankoral 25f05a69c6 Return new speckle_state from serializer 2023-02-17 02:16:54 +03:00
oguzhankoral 5c2a94da16 Align speckle entities with traversed objects 2023-02-17 02:16:54 +03:00
oguzhankoral 162325e90e Add desktop path to constants 2023-02-17 02:16:54 +03:00
oguzhankoral b022a8c608 Implement observers 2023-02-17 02:16:53 +03:00
oguzhankoral 1e404f5e6b Return converted and traversed versions from entities 2023-02-17 02:16:53 +03:00
oguzhankoral 8ef7780332 Write application_id to dictionary of SpeckleEntity 2023-02-17 02:16:53 +03:00
oguzhankoral 51b4f7b3f7 Use definition persistent id for nested block definitions 2023-02-17 02:16:53 +03:00
oguzhankoral 9b3fa33e50 Save traversed object details into SpeckleEntity's dictionary 2023-02-17 02:16:52 +03:00
oguzhankoral 9349d0813d Implement speckle_block_instance_entity 2023-02-17 02:16:52 +03:00
oguzhankoral 8d9bc500a1 Write traversed info to speckle entity's dictionary 2023-02-17 02:16:52 +03:00
oguzhankoral cae7b6e29f Add line and mesh speckle entities 2023-02-17 02:16:51 +03:00
oguzhankoral 0b4ac732b2 Fix traversal missing references 2023-02-17 02:16:51 +03:00
oguzhankoral acacbb91e3 Add single unit test for line traversing 2023-02-17 02:16:51 +03:00
oguzhankoral fd6d3d9a2f Pass state to converters 2023-02-17 02:16:51 +03:00
oguzhankoral 3b531e30b1 Disable caching temporarly 2023-02-17 02:16:50 +03:00
oguzhankoral 0c78085b2e Remove command data from console logging 2023-02-17 02:16:50 +03:00
oguzhankoral 4ab53308f7 Initialize relations on to_speckle 2023-02-17 02:16:50 +03:00
oguzhankoral 06ae161793 Check base object already traversed or not 2023-02-17 02:16:50 +03:00
oguzhankoral 94e005d2f8 Fix speckle_state immutable update on state 2023-02-17 02:16:49 +03:00
oguzhankoral 717072c3a5 Introduce speckle_entity objects 2023-02-17 02:16:49 +03:00
oguzhankoral 02b4bde92a Add speckle state for serialization process 2023-02-17 02:16:49 +03:00
oguzhankoral a397d1e233 Return speckle state from traversal methods 2023-02-17 02:16:45 +03:00
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
Oğuzhan Koral e76aeb80fd Fix (attributes): Use from_face method to face consider attributes 2023-01-11 12:05:47 +03:00
oguzhankoral 28292e59e2 Use from_face method to consider attributes 2023-01-11 11:59:50 +03:00
Oğuzhan Koral 25dda481b2 Comment out vertex count log 2023-01-10 16:47:42 +03:00
Oğuzhan Koral bbda233fd8 Comment out vertex count log 2023-01-10 16:46:56 +03:00
Oğuzhan Koral 349218f0b5 Feat (Mesh): mesh improvements
Mesh grouping methods are improved with options:

Shared vertices (It is not supported by viewer currently, but when available it is ready to approach)
Separated vertices
2023-01-10 14:36:11 +03:00
oguzhankoral f18d00a69d Remove disable rubocop issues 2023-01-10 14:30:28 +03:00
oguzhankoral 25ea6504de Note about when viewer supports shared vertices 2023-01-10 11:33:56 +03:00
oguzhankoral 43081c70e2 Send vertices separately 2023-01-10 10:51:03 +03:00
oguzhankoral 0fde1c2026 Optimize meshes with dynamic vertex adding 2023-01-10 09:14:11 +03:00
Oğuzhan Koral b35383571e Merge pull request #126 from specklesystems/gergo/updateCiContext
use innosetup context in the windows build
2023-01-09 14:57:18 +03:00
Oğuzhan Koral 45a84847a2 Fix (block): Base point for block definition 2023-01-08 17:41:56 +03:00
oguzhankoral 70d92f26d6 Note for reason to having block definition base points 2023-01-08 17:40:57 +03:00
oguzhankoral 737ed86e69 Comparison method for point object 2023-01-08 15:15:17 +03:00
oguzhankoral 3865057b7a Fallback geometry for block definition 2023-01-08 14:50:47 +03:00
oguzhankoral 42a84dcd86 Receive blocks from rhino 2023-01-07 00:35:02 +03:00
Gergő Jedlicska e2d819c59d Merge branch 'main' of github.com:specklesystems/speckle-sketchup into gergo/updateCiContext 2023-01-06 14:30:16 +01:00
oguzhankoral bfee6a88dc Add base point for block definition 2023-01-06 16:28:32 +03:00
Oğuzhan Koral 68f3be17df Fix (UI): closing UI cause state loss 2023-01-06 16:25:52 +03:00
oguzhankoral 929c97ff5e Bring to front dialog if it is minimized when user reclicked UI button 2023-01-06 16:20:53 +03:00
oguzhankoral 4b66a2e4d0 Reset dialog if it's closed 2023-01-06 16:20:32 +03:00
Gergő Jedlicska 46e740154e use innosetup context in the windows build 2023-01-06 14:03:50 +01:00
Oğuzhan Koral 05e89f49da Feat (attributes): Send/receive entity attributes 2023-01-06 04:39:47 +03:00
oguzhankoral 358e9071e3 Disable nested groups for now 2023-01-06 04:36:43 +03:00
oguzhankoral e37b6a1cc0 Fix receive groups as group
Previously groups were receiving as component
2023-01-06 03:45:21 +03:00
oguzhankoral 266721973b Check block instance and definition's sketchup_attributes 2023-01-06 03:17:45 +03:00
oguzhankoral 7c27ac85cb Check having sketchup_attributes already in line and mesh before apply dicts 2023-01-06 02:36:06 +03:00
oguzhankoral 4b79732e38 Comment out UI dev mode 2023-01-06 01:51:45 +03:00
oguzhankoral 2ceeea5298 Send attributes according to model preference 2023-01-06 01:41:41 +03:00
oguzhankoral 3ec659a59b Write definition dictionaries on receive 2023-01-06 01:22:58 +03:00
oguzhankoral 4309056851 Return if dictionaries nil 2023-01-06 01:22:58 +03:00
oguzhankoral b768f20f7a Include entity attributes on send/receive 2023-01-06 01:22:58 +03:00
oguzhankoral b3a71bcf53 Stage sqlite3 2 2023-01-06 01:22:58 +03:00
oguzhankoral 50c199bc03 Stage sqlite3 2023-01-06 01:22:58 +03:00
Oğuzhan Koral d6302ac128 Feat (settings): Settings dialog implemented 2023-01-06 01:22:36 +03:00
oguzhankoral 6a5d9e1394 Fix rubocop issues 2023-01-05 22:56:53 +03:00
oguzhankoral ac5fc3e6ea Improve positioning for switches 2023-01-05 22:50:18 +03:00
oguzhankoral aa6cbceeb9 Consider strategies on send/receive 2023-01-05 22:29:56 +03:00
oguzhankoral 46a7395382 Sync speckle user_state with UI 2023-01-05 22:29:56 +03:00
oguzhankoral a782811dad Assign preferences to data of App 2023-01-05 22:29:56 +03:00
oguzhankoral d22039bc96 Get rid of storing theming on localStorage
It stores now on database and sync with it
2023-01-05 22:29:56 +03:00
oguzhankoral 15539c258e Update config.db when theme has changed 2023-01-05 22:29:56 +03:00
oguzhankoral f9ca4acf16 Add unit tests for sqlite3 2023-01-05 22:29:56 +03:00
oguzhankoral 66d2a9b7fe Update sqlite3 submodule reference hash 2023-01-05 22:29:56 +03:00
oguzhankoral 6dff8c3221 Create test.db file for unit tests 2023-01-05 22:29:56 +03:00
oguzhankoral f13c65e083 Update sqlite3_27.so with read/write database 2023-01-05 22:29:56 +03:00
oguzhankoral 56a7d5cb86 Move theme to settings 2023-01-05 22:29:56 +03:00
oguzhankoral c63c0675d5 Init settings dialog 2023-01-05 22:29:56 +03:00
oguzhankoral 22bc4b8c9e Log upload time 2023-01-05 22:29:56 +03:00
Gergő Jedlicska ead17b8906 Merge pull request #122 from specklesystems/gergo/updateCiContext
add CI context reference to deploy job
2023-01-05 18:21:52 +01:00
Gergő Jedlicska 1a211daac2 make sure dir 2023-01-05 17:58:52 +01:00
Gergő Jedlicska c7e502da4e remove gh bot context 2023-01-05 17:56:53 +01:00
Gergő Jedlicska 70df5e6cec how touching 2023-01-05 17:55:56 +01:00
Gergő Jedlicska 793f287c35 make sure to create the known hosts 2023-01-05 17:53:46 +01:00
Gergő Jedlicska 8e8b1c60b8 use ssh cloning 2023-01-05 17:51:58 +01:00
Gergő Jedlicska ba2cd51852 rename gh token env var 2023-01-05 17:05:53 +01:00
Gergő Jedlicska 3986a4ef60 add gh devbot context 2023-01-05 17:03:38 +01:00
Gergő Jedlicska c99d89fb11 add CI context reference to deploy job 2023-01-05 16:59:23 +01:00
Oğuzhan Koral 0a9c33de91 Fix (Material): Send back material as fallback for front 2023-01-03 22:01:13 +03:00
oguzhankoral 88c940fc53 Send back material as fallback for front 2023-01-03 21:59:58 +03:00
Oğuzhan Koral 88c861cbde Fix (group): Add missing subgroup conversions 2023-01-03 21:19:44 +03:00
oguzhankoral cd071ca144 Fix missing group conversions 2023-01-03 21:17:37 +03:00
Oğuzhan Koral 9970b8ec36 Feat (Component): Store always face camera option for definitions 2023-01-02 22:24:58 +03:00
oguzhankoral ffc564becd Store always face camera option for defitions 2023-01-02 22:22:01 +03:00
Oğuzhan Koral 6289fd5941 Fix (blocks): Remove bbox and base_point from block objects 2023-01-02 18:05:12 +03:00
oguzhankoral 7f44fe76c7 Remove bbox and base_point from block objects 2023-01-02 18:03:50 +03:00
Oğuzhan Koral ea86dc6785 Fix (edge): Check all definition entities has any orphan edge 2023-01-02 15:56:33 +03:00
oguzhankoral 754f9e1ed1 Check all definition entities has any orphan edge 2023-01-02 15:51:54 +03:00
Oğuzhan Koral 9a1a02e664 Fix (accounts): Disable streams when there are no account and show message 2022-12-21 11:31:18 +03:00
oguzhankoral 1e92195355 Disable streams when there are no account and show message 2022-12-21 11:24:11 +03:00
Oğuzhan Koral bb63c1c990 Chore (Dependency) Update speckle object loader to 2.11.4
Update speckle object loader to 2.11.4
2022-12-21 00:45:56 +03:00
oguzhankoral 5af494de76 Update speckle object loader to 2.11.4
Receiving on big models there was null reference error on loader, which is fixed by Dim and Gergö
2022-12-20 18:54:48 +03:00
Oğuzhan Koral 5bcac73a45 Fix: Check object is hash before checking have speckle_object 2022-12-15 09:16:43 +03:00
oguzhankoral 7a709e48d7 Check object is hash before checking have speckle_object 2022-12-15 09:06:56 +03:00
Oğuzhan Koral d2d9fe010e Comment out console logging
Comment out console logging
2022-12-14 16:11:02 +03:00
oguzhankoral 4b6b129934 Comment out console logging 2022-12-14 16:10:03 +03:00
Oğuzhan Koral 7645146d77 Feat (DisplayValue): Support display value as base object
Support display value as base object
2022-12-14 16:07:09 +03:00
oguzhankoral 9b8cca38ae Support display value as base object 2022-12-14 16:04:23 +03:00
Oğuzhan Koral e872fda3f2 Feat (Group): Send meshes separately under the group
Feat (Group): Send meshes separately under the group
2022-12-13 14:01:49 +03:00
oguzhankoral 6ccb03c557 Improve clean up with scoped faces 2022-12-13 11:55:22 +03:00
oguzhankoral b8237b1be7 Send meshes separately under the group 2022-12-13 10:32:59 +03:00
Oğuzhan Koral 68e19cf8e4 Fix (type): Do not convert if entity type does not match with supported types
Fix (type): Do not convert if entity type does not match with supported types
2022-12-12 17:52:18 +03:00
oguzhankoral 91fba729b8 Return nothing if type does not match 2022-12-12 17:50:43 +03:00
Oğuzhan Koral 0af0e09b6e Fix (typo): Typo on keyword parameter values
Fix (typo): Typo on keyword parameter values
2022-12-12 17:38:04 +03:00
oguzhankoral 1276d56627 Fix typo 2022-12-12 17:37:00 +03:00
Oğuzhan Koral bd2773f9c1 Move base objects and their traversing process to Ruby
Move base objects and their traversing process to Ruby
2022-12-12 16:50:29 +03:00
oguzhankoral b8b1b8ef36 Fix rubocop issues and add documentation 2022-12-12 14:34:11 +03:00
oguzhankoral 0f98d72e30 Remove transport tests 2022-12-12 14:08:17 +03:00
oguzhankoral 44ec7734d4 Mark BaseObjectSerializer as deprecated in js 2022-12-12 13:52:57 +03:00
oguzhankoral c5adb063ca Add traversing benchmark for js 2022-12-12 13:18:42 +03:00
oguzhankoral 83ef8f9789 Benchmark for traversing 2022-12-12 13:03:55 +03:00
oguzhankoral b8a803a641 Replace ruby batches with javascript batches 2022-12-12 12:59:19 +03:00
oguzhankoral 31c0effebe Add inline documentation for serialization.js 2022-12-12 12:59:01 +03:00
oguzhankoral e90c4b3eb5 Traverse and serialize base object with send info 2022-12-12 12:57:07 +03:00
oguzhankoral ae6efcc27d Add sketchup unit value to sketchup state 2022-12-12 12:51:45 +03:00
oguzhankoral 7922ac127b Format callback message according to being string or not 2022-12-12 12:50:53 +03:00
oguzhankoral 559f0443af Inherit from Base for all Speckle objects 2022-12-12 12:43:25 +03:00
oguzhankoral 4e3faf1573 Port base object serializer to ruby from js 2022-12-12 12:42:30 +03:00
oguzhankoral e3039bd2d8 Add many to one relation immutable class 2022-12-08 17:49:24 +03:00
oguzhankoral e668cba839 Add immutable_ruby exterior library 2022-12-08 12:42:46 +03:00
oguzhankoral fcc47c0b7f Add dictionary handlers 2022-12-07 15:30:09 +03:00
oguzhankoral 2f189bc2ff Inherit from Base object for geometric speckle objects 2022-12-07 11:34:13 +03:00
oguzhankoral ee9d134f0e Line inherits from Base 2022-12-07 11:34:13 +03:00
oguzhankoral c1fad7cc02 Add test send batch for single line 2022-12-07 11:34:09 +03:00
Oğuzhan Koral 3a48b4ee4f Feat (Stream): Create stream by UI
Feat (Stream): Create stream by UI
2022-12-07 10:26:02 +03:00
oguzhankoral ec626f9741 Refresh streams after creation 2022-12-07 09:27:03 +03:00
oguzhankoral 90906cdc0e Disable Add a Stream by ID or URL dialog 2022-12-07 09:13:18 +03:00
oguzhankoral a694527645 Create stream on active account 2022-12-06 19:58:15 +03:00
oguzhankoral bd68b20d31 Add initial create stream component 2022-12-06 12:05:05 +03:00
Oğuzhan Koral 080d976400 Fix (Groups): Enable nested group send/receive
Fix (Groups): Enable nested group send/receive
2022-12-05 15:56:26 +03:00
oguzhankoral e200a8525b Enable nested group send/receive 2022-12-05 15:52:57 +03:00
Oğuzhan Koral 5bdf7abc2b Fix (Block): Fix missing references and typos
Fix (Block): Fix missing references and typos
2022-12-02 18:46:25 +03:00
oguzhankoral 9faa1d2e1e Fix typos 2022-12-02 18:41:20 +03:00
Oğuzhan Koral f586353cdb Feat (Layers): Support layers
Feat (Layers): Layers are available by Sketchup connector
2022-12-01 19:53:28 +03:00
oguzhankoral 2c9f2ee5cf Fix rubocop issues 2022-12-01 19:49:09 +03:00
oguzhankoral d8bd45bf54 Document layer implementations 2022-12-01 19:04:40 +03:00
oguzhankoral 9341e0b500 Remove commit layer folder 2022-12-01 18:08:09 +03:00
oguzhankoral eebf303f3e Introduce dynamic base object 2022-12-01 14:52:40 +03:00
oguzhankoral df487d5488 Pass layer information to native converter to place them into correct layers 2022-12-01 14:52:11 +03:00
oguzhankoral 7b66da5823 Pass stream and branch information to ruby
- This is needed when we create layers
2022-12-01 14:51:22 +03:00
oguzhankoral 3a929c2a03 Change target sketchup version with 2021 for rubocop 2022-12-01 14:50:04 +03:00
oguzhankoral a8ac3c4771 Send objects with layers to server 2022-11-30 23:58:23 +03:00
oguzhankoral 8d223786b9 Pass base object from ruby instead javascript 2022-11-30 23:57:55 +03:00
oguzhankoral 068c6a6ea0 Disable stripping @ for detaching 2022-11-30 23:57:34 +03:00
oguzhankoral 0db677121a Pass base object to BaseObjectSerializer prepared by ruby 2022-11-30 23:57:09 +03:00
Oğuzhan Koral 7591170bca Fix (Mesh): Do not merge SKP meshes
Fix (Mesh): Do not merge SKP meshes
2022-11-28 11:00:59 +03:00
oguzhankoral 7272541826 Check upcoming mesh comes from SKP or not 2022-11-28 10:53:32 +03:00
oguzhankoral 2460912532 Feat(Attributes): Store face soften property
This information will help us to create same geometry that sent.
2022-11-28 08:50:24 +03:00
oguzhankoral c1e4c22e3f Fix typo 2022-11-28 08:47:22 +03:00
oguzhankoral 17bd17fa3c Get/set correct face smooth properties from/to mesh 2022-11-25 21:40:39 +03:00
oguzhankoral 05da3ccb66 Add sketchup_attributes to speckle objects 2022-11-25 21:33:04 +03:00
oguzhankoral 578044884c Convert cleanup with static methods 2022-11-25 21:32:31 +03:00
oguzhankoral 226148d04f Chore(Readme): Update README with Repo Structure and contribution guidelines
Chore(Readme): Update README with Repo Structure and contribution guidelines
2022-11-25 11:54:39 +03:00
oguzhankoral c44c7516ff Update README with Repo Structure and contribution guidelines 2022-11-25 11:50:44 +03:00
oguzhankoral c3c0749222 Chore (sqlite3): Add sqlite3 project as submodule
Chore (sqlite3): Add sqlite3 project as submodule
2022-11-24 12:06:54 +03:00
oguzhankoral b7e63b0e54 Add sketchup-sqlite3 repo as submodule 2022-11-24 11:31:55 +03:00
oguzhankoral 60cd2cb0f9 Remove ported sqlite3 compiler from sketchup repo 2022-11-24 11:29:46 +03:00
oguzhankoral e6dd630caf Feat (sqlite3): compile sqlite3 from c as sketchup compatible
Feat (sqlite3): compile sqlite3 from c as sketchup compatible
2022-11-24 03:14:34 +03:00
oguzhankoral cad14b318a Use native sqlite3 module for queries 2022-11-24 03:04:34 +03:00
oguzhankoral 4df1cc17bf Add switch for operating system 2022-11-24 03:04:09 +03:00
oguzhankoral 884bb331b3 Add compiled sqlite3_27.so file for windows 2022-11-24 03:03:29 +03:00
oguzhankoral ff4a83af47 Add ruby version number constant 2022-11-24 03:02:58 +03:00
oguzhankoral f6f323b307 Remove externally compiled files, was workaround 2022-11-24 03:02:24 +03:00
oguzhankoral 330280d611 Implement conversion methods from C/C++ to Ruby for our connector 2022-11-24 03:01:30 +03:00
oguzhankoral 809432cbbd Feat (Converters): refactor and document converters
ToSpeckle and ToNative modules are refactored with classes that parallels to objects in the Speckle.

 Length
 Point
 Vector
 Plane
 BoundingBox
 Plane
 Mesh
 Transform
 RenderMaterial
 BlockDefinition
 BlockInstance
 Interval
2022-11-23 16:37:12 +03:00
oguzhankoral d64fee1d15 Refactor ToNative methods 2022-11-23 16:31:19 +03:00
oguzhankoral 3d01e15710 Pass data to reload_accounts action 2022-11-23 13:10:47 +03:00
oguzhankoral c2d7a5aca8 Extract block definition and block instance to related objects 2022-11-22 12:28:08 +03:00
oguzhankoral 2e8a040210 Implement speckle Mesh and RenderMaterial objects 2022-11-22 10:55:16 +03:00
oguzhankoral 50886147ae Use typescript objects as parent for speckle objects 2022-11-22 10:13:28 +03:00
oguzhankoral c0f2885de6 Introduce Speckle Geometry objects for conversion 2022-11-22 01:58:23 +03:00
oguzhankoral e80574ecd7 Add accounts to speckle state 2022-11-21 18:08:36 +03:00
oguzhankoral 7cdcf9e86f Get rid of from Sketchup.active_model and duplications 2022-11-21 17:52:05 +03:00
oguzhankoral 304720fc8e Add sketchup state to application state
- This will help to reach sketchup model instead of calling Sketchup.active_model everytime
2022-11-21 16:19:49 +03:00
oguzhankoral 88adfb1446 Fix(circle-ci): Rename html with vue_ui for built files
Fix(circle-ci): Rename html with vue_ui for built files
2022-11-20 15:02:05 +03:00
oguzhankoral f8719d0912 Rename html with vue_ui for built files 2022-11-20 15:01:23 +03:00
oguzhankoral c6d36fccd9 Fix: Save/Load streams locally
Fix: Save/Load streams locally
2022-11-17 16:24:00 +03:00
oguzhankoral 13d061f7e9 Remove unnessary calls 2022-11-17 16:19:58 +03:00
oguzhankoral ba11c5beb2 Fix: Disable one click send
Fix: Disable one click send
2022-11-17 09:32:45 +03:00
oguzhankoral c1800d9a02 Disable one click send 2022-11-17 09:30:13 +03:00
oguzhankoral 868859ac83 Feat(tools): add compiler project for sqlite3 and sqlite3 ruby files
* Ignore also old (html) ui built folder

* Ignore sqlite3 ext library ide settings

* Initialize sqlite3 project for .so and .bundle files

* Enable _MSC_VER check on 2.7

* Initialize ruby cpp transition file

* Communicate first time with sqlite3 c base code in Sketchup

* Update compiler project with sqlite3 cpp files

* Add sqlite3 ruby source files to ext libraries

* Update gitignore with Release and Debug folders of sqlite3 compiler

* Exclude rubocops for ext libraries

* Exclude rubocops for _sqlite3 compiler

* Fix rubocop issues
2022-11-16 14:18:28 +03:00
oguzhankoral d91114b340 Refactor: Implement all commands to new design
* Add command for receive_objects

* Fix path on sqlite3 gem

* Add commands

- init_local_accounts
- queue_send
- send_from_queue
- save_stream
- remove_stream
- reload_accounts
- load_saved_streams

* Disable other toolbar commands unless speckle initialized

* Introduce one click send
2022-11-11 21:54:23 +03:00
oguzhankoral 0dc4d46fa3 Refactor: Implement new plugin design
* Move icons to img folder

* Rename built folder name from html to vue_ui

* Rename main to bootstrap

* Remove unnecessary module for SpeckleSystems

* Replace dialog with App-View-Command design

* Fix rubocop issues

- disable to_native and to_speckle for now, they will be handled in different PR
2022-11-08 21:18:16 +03:00
oguzhankoral 0442aaaa7f CI: Add rubocop and rubycritic to CI and Rakefile to test locally
Oguzhan/setup code quality and ci
2022-11-07 10:33:59 +03:00
oguzhankoral 2c8b120de1 CI: Add rubocop and rubycritic to CI and Rakefile to test locally
Configure Gemfile.lock

Update README details about load/reload file(s)

Update version for only 2.7

Add linux and unknown platforms to Gemfile.lock

Add default task

Remove unnecssary dependencies from Rakefile

Assign default name to first task

Remove superseded cops

Update ruby version in workflow

Remove solorgraph and update it's dependencies

Update rubyversion

Exclude vendor bundle from rubocop

Autocorrected rubocop fixes

Extend SU related rubocop settings

Implement ruby critic for rake

Add documentation for code quality
2022-11-07 10:22:58 +03:00
oguzhankoral fc50ef68b4 Merge pull request #64 from specklesystems/oguzhan/setup-environment
Feat(tools): add tools to increase efficiency on dev mode
2022-11-01 21:03:42 +03:00
oguzhankoral 92613a37cd Feat(tools): add tools to increase efficiency on dev mode
Setup _tools for developer environment

Remove unnecassary lines on loader
2022-11-01 20:55:18 +03:00
Matteo Cominetti 77843fe697 ci: removes old deploy step 2022-10-28 09:22:39 +01:00
izzy lyseggen 9aea5ddc97 fix(metrics): remove suuid as it is no longer used (#60) 2022-09-07 10:08:29 +01:00
izzy lyseggen 4d1333c302 ci: add deploy for manager 2 (#59) 2022-08-25 16:20:28 +01:00
oguzhankoral 9ca55f6f0e Merge coplanar faces (#58)
* Update gitignore for RubyMine IDE

* Update gemfiles

- Include skippy, pry, rubycritic

* Add speckle_connector loader

This helps to navigate files that read by SU according to packaged version or development version

* Update gemfile and .lock files

* Merge coplanar faces by erasing edges between

* Cite about the idea behind merging coplanar faces

* Document remove_edge_have_coplanar_faces with control steps

* Improve loader file with the idea of release version

- This is another issue that need to be navigated

* Remove speckle_connector_loader
2022-08-18 14:42:16 +01:00
izzy lyseggen 4698799b43 ci: fix patch version script 2022-06-21 13:10:31 +01:00
izzy lyseggen 5d4e6ac89a ci: use semver tag for patching 2022-06-21 13:02:23 +01:00
izzy lyseggen b1c09c62d9 feat(converter): mesh enhancements (#50)
* feat(converter): smooth & preserve raw mesh

also only create components for `displayValue` geometry

wip - to try another edge strategy as this has caused quite a perf hit

* fix(converter): don't preserve edges for now

it is way too slow in the current implementation.
i think we can make it work in sketchup 2022 with the entities building,
but as of rn it seems like a no go for 2021
2022-06-21 12:58:59 +01:00
izzy lyseggen 2b7b74dbdd chore(ui): test removing core j (#48) 2022-06-20 16:28:07 +01:00
192 changed files with 15557 additions and 1160 deletions
+67 -26
View File
@@ -21,7 +21,7 @@ jobs:
- persist_to_workspace:
root: ./
paths:
- speckle_connector/html
- speckle_connector/vue_ui
build-connector: # Reusable job for basic connectors
executor:
@@ -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
python patch_version.py $version
speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss
$version = "$($ver).$($env:WORKFLOW_NUM)"
python patch_version.py $semver
environment:
WORKFLOW_NUM: << pipeline.number >>
- run:
name: Build Installer
command: speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss /Sbyparam=$p
shell: cmd.exe #does not work in powershell
#- run:
# name: Patch
# shell: powershell.exe
# command:
# | # If no tag, use 0.0.0.1 and don't make any YML (for testing only!)
# $tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "0.0.0" } else { $env:CIRCLE_TAG }
# $semver = if($tag.Contains('/')) {$tag.Split("/")[1] } else { $tag }
# $ver = if($semver.Contains('-')) {$semver.Split("-")[0] } else { $semver }
# $channel = if($semver.Contains('-')) {$semver.Split("-")[1] } else { "latest" }
# $version = "$($ver).$($env:CIRCLE_BUILD_NUM)"
# New-Item -Force "speckle-sharp-ci-tools/Installers/sketchup/$channel.yml" -ItemType File -Value "version: $semver"
# echo $version
# python patch_version.py $semver
# speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss
- persist_to_workspace:
root: ./
paths:
@@ -58,9 +80,17 @@ jobs:
docker:
- image: cimg/base:2021.01
steps:
- add_ssh_keys:
fingerprints:
- "03:2e:ee:4f:14:67:2b:88:32:e8:cc:f0:cb:df:92:29"
- run:
name: I know Github as a host
command: |
mkdir ~/.ssh
ssh-keyscan github.com >> ~/.ssh/known_hosts
- run:
name: Clone
command: git clone https://$GITHUB_TOKEN@github.com/specklesystems/speckle-sharp-ci-tools.git speckle-sharp-ci-tools
command: git clone git@github.com:specklesystems/speckle-sharp-ci-tools.git speckle-sharp-ci-tools
- persist_to_workspace:
root: ./
paths:
@@ -69,23 +99,29 @@ jobs:
root: ./
paths:
- speckle-sharp-ci-tools
deploy: # Uploads all installers found to S3
deploy-manager2:
docker:
- image: cimg/base:2021.01
- image: mcr.microsoft.com/dotnet/sdk:6.0
parameters:
slug:
type: string
os:
type: string
extension:
type: string
steps:
- checkout
- attach_workspace:
at: ./
- run:
name: List contents
command: ls -R speckle-sharp-ci-tools/Installers
- aws-s3/copy:
arguments: "--recursive --endpoint=https://$SPACES_REGION.digitaloceanspaces.com --acl public-read"
aws-access-key-id: SPACES_KEY
aws-region: SPACES_REGION
aws-secret-access-key: SPACES_SECRET
from: '"speckle-sharp-ci-tools/Installers/"'
to: s3://speckle-releases/installers/
name: Install Manager Feed CLI
command: dotnet tool install --global Speckle.Manager.Feed
- run:
name: Upload new version
command: |
TAG=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "0.0.0"; fi;)
SEMVER=$(echo "$TAG" | sed -e 's/\/[a-zA-Z-]*//')
/root/.dotnet/tools/Speckle.Manager.Feed deploy -s << parameters.slug >> -v ${SEMVER} -u https://releases.speckle.dev/installers/<< parameters.slug >>/<< parameters.slug >>-${SEMVER}.<< parameters.extension >> -o << parameters.os >> -f speckle-sharp-ci-tools/Installers/<< parameters.slug >>/<< parameters.slug >>-${SEMVER}.<< parameters.extension >>
workflows:
build-and-deploy:
@@ -108,8 +144,13 @@ workflows:
filters:
tags:
only: /.*/
context: innosetup
- deploy:
- deploy-manager2:
context: do-spaces-speckle-releases
slug: sketchup
os: Win
extension: exe
requires:
- get-ci-tools
- build-ui
+38
View File
@@ -0,0 +1,38 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
name: Ruby
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
ruby-version: ['2.7']
steps:
- uses: actions/checkout@v3
- name: Set up Ruby
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
# change this to (see https://github.com/ruby/setup-ruby#versioning):
# uses: ruby/setup-ruby@v1
uses: ruby/setup-ruby@0a29871fe2b0200a17a4497bae54fe5df0d973aa # v1.115.3
with:
ruby-version: ${{ matrix.ruby-version }}
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Run tests
run: bundle exec rake
+19 -1
View File
@@ -10,8 +10,23 @@
settings.json
# vue app build dist folder
speckle_connector/vue_ui
speckle_connector/html
# speckle-sharp-ci-tools
/speckle-sharp-ci-tools
# _sqlite3
/_sqlite3/.vs
/_sqlite3/Release (2.7)
/_sqlite3/Release (2.5)
/_sqlite3/Release (2.2)
/_sqlite3/Release (2.0)
/_sqlite3/Debug (2.7)
/_sqlite3/Debug (2.5)
/_sqlite3/Debug (2.2)
/_sqlite3/Debug (2.0)
*.gem
*.rbc
/.config
@@ -24,6 +39,9 @@ speckle_connector/html
/test/version_tmp/
/tmp/
# IDE
.idea
# Used by dotenv library to load environment variables.
.env
+3
View File
@@ -0,0 +1,3 @@
[submodule "_sqlite3"]
path = _sqlite3
url = git@github.com:specklesystems/sketchup-sqlite3.git
+108 -25
View File
@@ -1,49 +1,132 @@
require:
- rubocop-sketchup
- rubocop-minitest
- rubocop-rake
AllCops:
TargetRubyVersion: 2.7
EnabledByDefault: true
AutoCorrect: true
TargetRubyVersion: 2.5
DisabledByDefault: false
NewCops: enable
DisplayCopNames: true
ExtraDetails: true
SuggestExtensions: false
Exclude:
- '_tools/jf_RubyPanel.rb'
- '_tools/jf_RubyPanel/**/*.rb'
- '_tools/su_attributes.rb'
- '_tools/su_attributes/**/*.rb'
- '_tools/su_attributes/**/*.rb'
- '_sqlite3/**/*.rb'
- 'ui/**/*'
- 'speckle_connector/src/ext/**/*.rb'
- 'vendor/bundle/**/*'
- 'tests/**/*.rb'
SketchUp:
SourcePath: .
TargetSketchUpVersion: 2021
Exclude: # Exclude common folders.
- 'tests/**/*'
- 'benchmarks/**/*'
- '_tools/**/*'
- 'Rakefile'
Style/StringLiterals:
Layout:
Enabled: true
EnforcedStyle: double_quotes
Style/StringLiteralsInInterpolation:
Layout/IndentationStyle:
EnforcedStyle: spaces
IndentationWidth: 2
# If DisabledByDefault is set to true then we need to enable the SketchUp
# related departments:
SketchupDeprecations:
Enabled: true
EnforcedStyle: double_quotes
Layout/LineLength:
Max: 120
SketchupPerformance:
Enabled: true
Lint/ConstantResolution:
SketchupRequirements:
Enabled: true
SketchupSuggestions:
Enabled: true
SketchupBugs:
Enabled: true
SketchupRequirements/FileStructure:
Enabled: false
Style/Copyright:
Enabled: false
Style/DocumentationMethod:
SketchupSuggestions/ModelEntities:
Enabled: false
Metrics/AbcSize:
Enabled: false
Max: 30
Metrics/BlockLength:
Enabled: false
Metrics/ClassLength:
Enabled: false
Metrics/ModuleLength:
Enabled: false
# Exclude spec tests
Exclude:
- "**/*_spec.rb"
Metrics/MethodLength:
Max: 20
Metrics/ClassLength:
Max: 200
Layout/EndOfLine:
Enabled: false
EnforcedStyle: lf
Minitest/MultipleAssertions:
Max: 5
Naming/MethodParameterName:
AllowedNames: [x, y, z, id]
Naming/VariableNumber:
EnforcedStyle: snake_case
# SketchUp 2017 uses Ruby 2.2 where safe navigation is not available
Style/SafeNavigation:
Enabled: false
Metrics/ParameterLists:
Style/AndOr:
Enabled: false
Metrics/CyclomaticComplexity:
Style/Documentation:
Exclude:
- "*tests/**/*_spec.rb"
- "*tests/**/*_test.rb"
Style/Not:
Enabled: false
Metrics/PerceivedComplexity:
Style/NumericLiterals:
Enabled: false
Style/NumericPredicate:
EnforcedStyle: comparison
Style/Proc:
Enabled: false
Style/RedundantReturn:
Enabled: false
# SketchUp 2017 uses Ruby 2.2 where safe navigation is not available
Style/SlicingWithRange:
Enabled: false
# SketchUp 2017 uses Ruby 2.2 where transform_values is not available
Style/HashTransformValues:
Enabled: false
# SketchUp 2017 uses Ruby 2.2 where transform_keys is not available
Style/HashTransformKeys:
Enabled: false
# SketchUp 2017 uses Ruby 2.2 where block needs to be wrapped in begin/end if ensure can be used
Style/RedundantBegin:
Enabled: false
+24 -11
View File
@@ -1,16 +1,29 @@
# frozen_string_literal: true
source "https://rubygems.org"
# gem "rake", "~> 13.0"
gem "rubocop", "~> 1.7"
source 'https://rubygems.org'
group :development do
gem "minitest"
gem "sketchup-api-stubs"
gem "solargraph"
# mini tests for ruby classes
gem 'minitest'
# Git hooks manager
gem 'overcommit', require: false
# Pry is a runtime developer console and IRB alternative with powerful introspection capabilities.
# Pry aims to be more than an IRB replacement. It is an attempt to bring REPL driven programming to the Ruby language.
gem 'pry'
# Make-like program implemented in Ruby. Tasks and dependencies are specified in standard Ruby syntax.
gem 'rake'
# RuboCop is a Ruby static code analyzer (a.k.a. linter) and code formatter.
gem 'rubocop'
# A RuboCop extension focused on enforcing Minitest best practices and coding conventions.
gem 'rubocop-minitest'
# A RuboCop plugin for Rake.
gem 'rubocop-rake'
# Code analysis for SketchUp extensions using the SketchUp Ruby API.
gem 'rubocop-sketchup'
# wraps around static analysis gems such as Reek, Flay and Flog to provide a quality report of your Ruby code.
gem 'rubycritic', '~> 4.3', '>= 4.3.3', require: false
# Auto completions for SketchUp API.
gem 'sketchup-api-stubs'
# Aid with common SketchUp extension tasks.
gem 'skippy', '~> 0.4.1.a'
end
gem "sqlite3", "~> 1.4"
+112 -52
View File
@@ -1,74 +1,134 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
backport (1.2.0)
benchmark (0.1.1)
diff-lcs (1.4.4)
e2mmap (0.1.0)
jaro_winkler (1.5.4)
kramdown (2.3.1)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
minitest (5.14.4)
nokogiri (1.12.5-x64-mingw32)
racc (~> 1.4)
nokogiri (1.12.5-x86_64-linux)
racc (~> 1.4)
parallel (1.20.1)
parser (3.0.2.0)
axiom-types (0.1.1)
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
childprocess (4.1.0)
coderay (1.1.3)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
docile (1.4.0)
equalizer (0.0.11)
erubi (1.11.0)
flay (2.13.0)
erubi (~> 1.10)
path_expander (~> 1.0)
ruby_parser (~> 3.0)
sexp_processor (~> 4.0)
flog (4.6.6)
path_expander (~> 1.0)
ruby_parser (~> 3.1, > 3.1.0)
sexp_processor (~> 4.8)
git (1.12.0)
addressable (~> 2.8)
rchardet (~> 1.8)
ice_nine (0.11.2)
iniparse (1.5.0)
kwalify (0.7.2)
launchy (2.5.0)
addressable (~> 2.7)
method_source (1.0.0)
minitest (5.16.3)
naturally (2.2.1)
overcommit (0.59.1)
childprocess (>= 0.6.3, < 5)
iniparse (~> 1.4)
rexml (~> 3.2)
parallel (1.22.1)
parser (3.1.2.1)
ast (~> 2.4.1)
racc (1.6.0)
rainbow (3.0.0)
regexp_parser (2.1.1)
reverse_markdown (2.0.0)
nokogiri
path_expander (1.1.1)
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
psych (3.3.4)
public_suffix (5.0.0)
rainbow (3.1.1)
rake (13.0.6)
rchardet (1.8.0)
reek (6.1.1)
kwalify (~> 0.7.0)
parser (~> 3.1.0)
rainbow (>= 2.0, < 4.0)
regexp_parser (2.6.0)
rexml (3.2.5)
rubocop (1.19.1)
rubocop (1.7.0)
parallel (~> 1.10)
parser (>= 3.0.0.0)
parser (>= 2.7.1.5)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml
rubocop-ast (>= 1.9.1, < 2.0)
rubocop-ast (>= 1.2.0, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 3.0)
rubocop-ast (1.11.0)
parser (>= 3.0.1.1)
unicode-display_width (>= 1.4.0, < 2.0)
rubocop-ast (1.4.1)
parser (>= 2.7.1.5)
rubocop-minitest (0.23.0)
rubocop (>= 0.90, < 2.0)
rubocop-rake (0.6.0)
rubocop (~> 1.0)
rubocop-sketchup (1.3.0)
rubocop (>= 0.82, < 2.0)
ruby-progressbar (1.11.0)
sketchup-api-stubs (0.7.7)
solargraph (0.43.0)
backport (~> 1.2)
benchmark
bundler (>= 1.17.2)
diff-lcs (~> 1.4)
e2mmap
jaro_winkler (~> 1.5)
kramdown (~> 2.3)
kramdown-parser-gfm (~> 1.1)
parser (~> 3.0)
reverse_markdown (>= 1.0.5, < 3)
rubocop (>= 0.52)
thor (~> 1.0)
tilt (~> 2.0)
yard (~> 0.9, >= 0.9.24)
sqlite3 (1.4.2)
thor (1.1.0)
tilt (2.0.10)
unicode-display_width (2.0.0)
yard (0.9.26)
ruby_parser (3.19.1)
sexp_processor (~> 4.16)
rubycritic (4.7.0)
flay (~> 2.8)
flog (~> 4.4)
launchy (>= 2.0.0)
parser (>= 2.6.0)
rainbow (~> 3.0)
reek (~> 6.0, < 7.0)
ruby_parser (~> 3.8)
simplecov (>= 0.17.0)
tty-which (~> 0.4.0)
virtus (~> 1.0)
sexp_processor (4.16.1)
simplecov (0.21.2)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
sketchup-api-stubs (0.7.8)
skippy (0.4.3.a)
git (~> 1.3)
naturally (~> 2.1)
thor (~> 0.19)
thor (0.20.3)
thread_safe (0.3.6)
tty-which (0.4.2)
unicode-display_width (1.8.0)
virtus (1.0.5)
axiom-types (~> 0.1)
coercible (~> 1.0)
descendants_tracker (~> 0.0, >= 0.0.3)
equalizer (~> 0.0, >= 0.0.9)
PLATFORMS
x64-mingw32
x64-unknown
x86_64-linux
DEPENDENCIES
minitest
rubocop (~> 1.7)
overcommit
pry
rake
rubocop
rubocop-minitest
rubocop-rake
rubocop-sketchup
rubycritic (~> 4.3, >= 4.3.3)
sketchup-api-stubs
solargraph
sqlite3 (~> 1.4)
skippy (~> 0.4.1.a)
BUNDLED WITH
2.2.26
2.3.25
+86 -30
View File
@@ -41,27 +41,42 @@ Give Speckle a try in no time by:
- [![docs](https://img.shields.io/badge/docs-speckle.guide-orange?style=for-the-badge&logo=read-the-docs&logoColor=white)](https://speckle.guide/user/blender.html) reference on almost any end-user and developer functionality
# Repo structure
# Repo structure
This is the beginning of the Speckle SketchUp Connector. It is still in very early development and is not ready for general use.
This repo is split into three parts:
This repo is split into two parts: `speckle_connector` which is the Ruby SketchUp plugin and `ui` which is the Vue frontend.
### 1. **Speckle Connector extension**
Includes the `ruby` source files to run extension on SketchUp environment. SketchUp Extensions are composed of
a **.rb** file as entry and **folder** that .rb file refers to. In our case entry file is `speckle_connector.rb`
that responsible to register Speckle Connector extension to SketchUp and also it shows address to where extension
will start to read extension. Source folder is `speckle_connector`.
## Usage
### 2. **User Interface**
> NOTE: this connector is still in early development and isn't ready for general use.
Includes the `Vue` frontend lives in the `ui` folder.
Copy the whole `speckle_connector` folder to you SketchUp Plugins folder. You will likely find this at:
### 3. **SketchUp Sqlite3 extension** [submodule](https://github.com/specklesystems/sketchup-sqlite3)
C:\Users\{YOU}\AppData\Roaming\SketchUp\SketchUp 2021\SketchUp\Plugins
Includes source codes of base `SQLite3` C/C++ library and `ruby` compiler files to be able to run SQLite3
functionality on SketchUp in the same ruby module like `SpeckleConnector::Sqlite3::Database`. By this way
we use extensions as native part of the source `ruby` code.
After building `sqlite3.sln` file, compiled `sqlite3.so` (for Windows) and `sqlite3.bundle` (for OSX) dynamic library files are created
by solution to place them into source code into `speckle_connector/src/ext`. Building this project should be only
happen when SketchUp starts to support newer Ruby versions (currently it is `2.7`).
You'll need to serve the ui before launching the connector:
## Contribution Guide
cd ui
npm install
npm run serve
Before start to contribute, it is better to understand how align with other contributors. It will make easier job
of reviewer when you submit an issue or PR. If it is your first repo to contribute Speckle environment make sure that you read
[Contribution Guideline](https://github.com/specklesystems/speckle-sharp/blob/main/.github/CONTRIBUTING.md).
Additionally as mentioned on [Repo Structure](#3-sketchup-sqlite3-extension-submodulehttpsgithubcomspecklesystemssketchup-sqlite3),
this repo includes a submodule. Contributions on this source files should be done on the [sketchup-sqlite](https://github.com/specklesystems/sketchup-sqlite3)
by creating issues and PRs on it. If it is your first time works with submodules, please read [git docs](https://git-scm.com/book/en/v2/Git-Tools-Submodules)
briefly to get some insight about it.
## Development
@@ -89,43 +104,63 @@ Clone this repo and run:
This will install all the necessary packages for the connector.
Next, install the Sketchup Ruby Debugger. You can find installation instructions [here](https://github.com/SketchUp/sketchup-ruby-debugger/blob/main/README.md). This will involve downloading the `dll` and copying it into the SketchUp installation directory:
Next, install the Sketchup Ruby Debugger. You can find installation instructions
[here](https://github.com/SketchUp/sketchup-ruby-debugger/blob/main/README.md).
This will involve downloading the `dll` and copying it into the SketchUp installation
directory:
C:\Program Files\SketchUp\SketchUp 2021\
C:\Program Files\SketchUp\SketchUp 20XX\
You can now open up the repo in VS Code.
You can now open up the repo in VS Code or you can use JetBrains' tools RubyMine and Webstorm.
Make sure you've installed the Ruby extension for VS Code.
If you will use VS Code, make sure you've installed the Ruby extension for VS Code.
### Loading the Plugin
### Loading the Speckle Connector Plugin
To tell SketchUp to load the plugin from wherever you happen to be developing, you'll need to create a ruby file with the following contents:
1. Find already prepared `speckle_connector_loader.rb` file on the `_tools`
folder.
2. Copy this Ruby file into your SketchUp Plugins directory. You will likely find this at:
`C:\Users\{YOU}\AppData\Roaming\SketchUp\SketchUp 20XX\SketchUp\Plugins`
3. Update below line on the copied file with your local git file.
```ruby
speckle_path = File.join(home_folder, 'Git', 'Speckle', 'speckle-sketchup')
```
By this way SketchUp will directly read your local repository. Do not forget,
this file also loads additional tools on the `_tools` folder.
Those are will be only available on dev mode.
```ruby
$LOAD_PATH << 'C:\YOUR\PATH\TO\THE\sketchup_connector'
require 'speckle_connector.rb'
```
Due to the fact that Ruby is interpreted language, so you can reload your file(s) when
you changed them. There are different kinds of ways to reload them.
Drop this Ruby file into your SketchUp Plugins directory. You will likely find this at:
1. To reload the whole plugin files while SketchUp is running, open up the Ruby console
and run the following:
```ruby
SpeckleConnector.reload
```
2. To reload only specific files, use `jf ruby toolbar` plugin that already available
on SketchUp toolbar.
C:\Users\{YOU}\AppData\Roaming\SketchUp\SketchUp 2021\SketchUp\Plugins
### User Interface
To reload the plugin while SketchUp is running, open up the Ruby console and run the following:
If it is your first time you cloned the project and willing to see Speckle UI, you
should make sure that you compiled the `vue.js` project in the `ui` folder.
SpeckleSystems::SpeckleConnector.reload
To run the `ui`, create a `.env` based on `.env-example` and paste in your Speckle token. Then:
To run the `ui`, create a `.env` based on `.env-example` and paste in your
Speckle token. Then:
cd ui
npm run serve
### Debugging
To run SketchUp in debug mode, you will run the task specified in `tasks.json`. Before you do this, make sure your integrated shell for tasks is using powershell. You can specify this by adding the following option to your workspace's `settings.json`
To run SketchUp in debug mode, you will run the task specified in `tasks.json`.
Before you do this, make sure your integrated shell for tasks is using powershell.
You can specify this by adding the following option to your workspace's `settings.json`
"terminal.integrated.automationShell.windows": "powershell.exe",
To start the task, use the keyboard shortcut `ctrl` + `shift` + `p` to open up the Command Palette. Search for `Tasks: Run Task` and select it:
To start the task, use the keyboard shortcut `ctrl` + `shift` + `p` to open up
the Command Palette. Search for `Tasks: Run Task` and select it:
![command palette](https://user-images.githubusercontent.com/7717434/135051668-35fee34e-5270-4b83-9c7b-dabb872370ee.png)
@@ -133,9 +168,30 @@ Then choose the `Debug Sketchup 2021` task to run it:
![debug sketchup task](https://user-images.githubusercontent.com/7717434/135051777-4c350a62-45fb-400e-9b24-4fbb02331b83.png)
Once Sketchup has launched, start the `Listen for rdebug-ide` debug configuration. Once the debugger has connected, you'll be able to debug the connector normally.
Once Sketchup has launched, start the `Listen for rdebug-ide` debug configuration.
Once the debugger has connected, you'll be able to debug the connector normally.
Make sure you run the `ui` before starting the SketchUp Connector
cd ui
npm run serve
npm run serve
### Code Quality
Tracking your code quality before merging any code to `main` branch might not seem at the
first time crucial, but when repo became huge, you might have many spaghetti code and technical
depth. It is always better to keep your work tough from the beginning. For this reason some
workflows have already setup on CI, those workflows must be passed before considering to
merge.
To track your code quality locally,
1. Make sure that you do not have any RuboCop issue, run below
```ruby
bundle exec rake
```
2. To check overall state of repository by RubyCritic, run below
```ruby
rake rubycritic
```
+52
View File
@@ -0,0 +1,52 @@
# frozen_string_literal: true
require 'rake/testtask'
require 'rubocop/rake_task'
require 'rubycritic/rake_task'
module SpeckleConnector
# Custom utility functions for rake tasks
module RakeUtils
module_function
# Find ruby files that were changed from `main` to the latest revision
def changed_rb_files(previous_revision: 'main', latest_revision: '')
range = latest_revision.empty? ? previous_revision : "#{latest_revision}..#{previous_revision}"
command = "git diff #{range} --name-only"
changed_files = `#{command}`.split("\n")
# filter changed files with ruby files (.rb), Gemfile and Rakefile.
filtered_files = changed_files.grep(/.*\.rb$|Gemfile|Rakefile/)
filtered_files.select { |file| File.exist?(file) }
end
end
end
# Add default rubocop task
RuboCop::RakeTask.new(:default)
# Add task to only verify ruby files that are different than in the `main` branch
desc('Run rubocop on changed files')
RuboCop::RakeTask.new(:rubocop_changed) do |t|
t.patterns = FileList.new(SpeckleConnector::RakeUtils.changed_rb_files)
end
# Glob pattern to match source files. Defaults to FileList['.'].
ruby_critic_paths = FileList[
'speckle_connector/**/*.rb',
'speckle_connector.rb',
'tests/**/*.rb'] -
FileList[
'_tools/**/*.rb',
'speckle_connector/src/ext/**/*.rb',
]
# for local
RubyCritic::RakeTask.new('rubycritic') do |task|
task.paths = ruby_critic_paths
end
# for CI
RubyCritic::RakeTask.new('rubycritic-ci') do |task|
task.options = '--mode-ci --format console --no-browser --branch main'
task.paths = ruby_critic_paths
end
Submodule
+1
Submodule _sqlite3 added at 800dd5e366
+25
View File
@@ -0,0 +1,25 @@
# Tools
This folder stores the external tools and helper scripts to make easier life of the developer,
they are not the part of the main functionality of the Speckle.
Tools and scripts inside the folder will be loaded with `sketchup_connector_loader.rb` file.
In order to load your own `.rb` files please add this file names into list in the loader.
````ruby
...
files = %w[speckle_connector jf_RubyPanel su_attributes <put-your-file-here>]
# This line placed before loading started.
files.each do |ruby_file|
puts "Loading #{ruby_file}"
begin
require ruby_file
rescue LoadError
puts "Could not load #{ruby_file}"
end
end
````
Track load status of your tools and scripts on the ruby console when SketchUp UI initializing.
+29
View File
@@ -0,0 +1,29 @@
# frozen_string_literal: true
# #-------------------------------------------------------------------------------------------------
# *************************************************************************************************
# RubyPanel Toolbar (C) 2007 jim.foltz@gmail.com
#
# With special thanks to Chris Phillips (Sketchy Physics)
# for the Win32API code examples.
#
# 2011-01-05 <jim.foltz@gmail.com>
# * Changed Toolbar name from "Ruby COnsole" to "Ruby Toolbar" (TT)
# http://forums.sketchucation.com/viewtopic.php?f=323&t=1542&p=298587#p298587
# * Wrapped in addition module RubyToolbar
# * Use $suString.GetSting to get proper "Ruby Console" name string.
# * Better check if TB was previously visible
# * Use UI.start_timer to restore Toolbar
# ICONS: located in the subfolder "rubytoolbar"
# MODIFICATION: by Fredo6 for compliance with SU 2014 (and no dependency on Win32API) - 18 Sep 2013
# *************************************************************************************************
#-------------------------------------------------------------------------------------------------
require 'sketchup'
require 'extensions'
ext = SketchupExtension.new('Ruby Toolbar', 'jf_RubyPanel/rubytoolbar.rb')
ext.creator = 'Jim Foltz <jim.foltz@gmail.com>'
ext.description = 'Toolbar for manipulating the Ruby Console. Compatible with SketchUp 2014'
ext.version = '2014'
Sketchup.register_extension(ext, true)
Binary file not shown.

After

Width:  |  Height:  |  Size: 934 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

+89
View File
@@ -0,0 +1,89 @@
# frozen_string_literal: true
#-------------------------------------------------------------------------------------------------------------------------------------------------
# RubyPanel Toolbar (C) 2007 jim.foltz@gmail.com
# Permission to use, copy, modify, and distribute this software for # any purpose and without fee is hereby granted,
# provided that the above copyright notice appear in all copies.
# THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION,
# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
# Description: Manage the loading of Ruby files and display of the Ruby console
# CREDITS: Special thanks to Chris Phillips (Sketchy Physics) for the Win32API code examples
# Revision: 3 Aug 2009, by Fredo6
# ICONS: located in the subfolder "rubytoolbar"
# MODIFICATION: by Fredo6 for compliance with SU 2014 (and no dependency on Win32API) - 18 Sep 2013
#-------------------------------------------------------------------------------------------------------------------------------------------------
require 'English'
require 'sketchup'
module JF_RubyToolbar
# Load the toolbar icons and commands, and do some initialization
def self.load_toolbar
@last_dir = "#{$LOAD_PATH[0]}/"
@last_dir = @last_dir.gsub('/', '\\\\\\\\')
@last_dir = File.join($JF_RUBYTOOLBAR, 'speckle_connector')
curdir = File.dirname __FILE__
# create toolbar
tb = UI::Toolbar.new 'Ruby Toolbar'
# Toggle console
cmd = UI::Command.new('Show/Hide') { SKETCHUP_CONSOLE.visible? ? SKETCHUP_CONSOLE.hide : SKETCHUP_CONSOLE.show }
cmd.large_icon = cmd.small_icon = File.join(curdir, 'rubypanel.png')
cmd.status_bar_text = cmd.tooltip = 'Show/Hide Ruby Console'
tb.add_item cmd
# Clear Console
cmd = UI::Command.new('Clear') { SKETCHUP_CONSOLE.clear }
cmd.status_bar_text = cmd.tooltip = 'Clear Console'
cmd.large_icon = cmd.small_icon = File.join(curdir, 'Delete24.png')
tb.add_item cmd
# Load a Ruby script
cmd = UI::Command.new('LoadScript') { load_script }
cmd.large_icon = cmd.small_icon = File.join(curdir, 'doc_ruby.png')
cmd.tooltip = cmd.status_bar_text = 'Load Script'
tb.add_item cmd
# Reload the last Ruby Script
@cmd_reload = UI::Command.new('Reload') { load_script @last_file }
@cmd_reload.large_icon = @cmd_reload.small_icon = File.join(curdir, 'reload.png')
@cmd_reload.status_bar_text = @cmd_reload.tooltip = 'Reload Script'
tb.add_item @cmd_reload
# Open the SU plugins directory panel
cmd = UI::Command.new('PluginsDir') { UI.openURL @last_dir }
cmd.tooltip = cmd.status_bar_text = 'Browse Plugins Folder'
cmd.large_icon = cmd.small_icon = File.join(curdir, 'open_folder.png')
tb.add_item cmd
# showing the toolbar
tb.get_last_state == -1 ? tb.show : tb.restore
end
# Load a script file - if <file> is nil, open the dialog panel to select the file
def self.load_script(file = nil)
file ||= UI.openpanel 'Load Script', @last_dir, '*.rb*'
return unless file
begin
load file
Sketchup.set_status_text "#{File.basename(file)} loaded (#{Time.now.strftime('%H:%M:%S')})"
@last_file = file
@last_dir = "#{File.dirname(file)}/"
@last_dir = @last_dir.gsub('/', '\\\\\\\\')
@cmd_reload.status_bar_text = @cmd_reload.tooltip = "Reload Script: #{File.basename(file)}"
rescue StandardError
UI.messagebox("Couldn't load #{File.basename(file)}: #{$ERROR_INFO}")
end
end
# Loading the toolbar once
unless file_loaded?('RubyToolbar.rb')
load_toolbar
file_loaded('RubyToolbar.rb')
end
end
+44
View File
@@ -0,0 +1,44 @@
# frozen_string_literal: true
# The purpose of this file is customizing environment of the developer on SketchUp.
# Each developer can customize it's own loader(this file), by this way developer can load their helper tools
# and helper methods ONLY in dev mode.
# Change the base folder and copy this file to Sketchup Plugins directory
# If you need to test in several versions of SketchUp, create symlinks to this file
# ( AppData\Roaming\SketchUp\SketchUp <version>\SketchUp\Plugins )
# Create a link to Plugins folder with this command
# rubocop:disable Layout/LineLength
# New-Item -ItemType SymbolicLink -Path '~\AppData\Roaming\SketchUp\SketchUp 2022\SketchUp\Plugins\speckle_connector_loader.rb' -Target ~\Git\Speckle\speckle-sketchup\_tools\speckle_connector_loader.rb
# rubocop:enable Layout/LineLength
SKETCHUP_CONSOLE.show # if you want to show Ruby console on startup
# base location of your repos - will be merged with specific repos in next step
home_folder = File.expand_path('~')
# If you use some other location for your repository, you can change it here
# but make sure it is not committed as it will change thi setting for all
# users that use the default setup. Eg:
# Add Speckle folder - uncomment the one you need
speckle_path = File.join(home_folder, 'Git', 'Speckle', 'speckle-sketchup')
$LOAD_PATH << speckle_path
$LOAD_PATH << File.join(speckle_path, '_tools')
# Defining this path will help to tool to browse related source file directly when
# developer attempted to reload/load file.
# rubocop:disable Style/GlobalVars
$JF_RUBYTOOLBAR = speckle_path
# rubocop:enable Style/GlobalVars
files = %w[speckle_connector jf_RubyPanel su_attributes]
files.each do |ruby_file|
puts "Loading #{ruby_file}"
begin
require ruby_file
rescue LoadError
puts "Could not load #{ruby_file}"
end
end
+49
View File
@@ -0,0 +1,49 @@
# Copyright 2014-2021, Trimble Inc.
#
# License: The MIT License (MIT)
#
# A SketchUp Ruby Extension that surfaces attributes attached to components.
# More info at https://github.com/SketchUp/sketchup-attribute-helper
require 'sketchup.rb'
require 'extensions.rb'
#-------------------------------------------------------------------------------
module Trimble
module AttributeHelper
### CONSTANTS ### ------------------------------------------------------------
# Plugin information
PLUGIN_ID = 'AttributeHelper'.freeze
PLUGIN_NAME = 'SketchUp Attribute Helper'.freeze
PLUGIN_VERSION = '1.0.3'.freeze
# Resource paths
FILENAMESPACE = File.basename(__FILE__, '.*')
PATH_ROOT = File.dirname(__FILE__).freeze
PATH = File.join(PATH_ROOT, FILENAMESPACE).freeze
### EXTENSION ### ------------------------------------------------------------
unless file_loaded?(__FILE__)
loader = File.join( PATH, 'core.rb' )
ex = SketchupExtension.new(PLUGIN_NAME, loader)
ex.description = 'Visually inspect nested attributes in SketchUp.'
ex.version = PLUGIN_VERSION
ex.copyright = 'Trimble Inc © 2015-2021'
ex.creator = 'SketchUp'
Sketchup.register_extension(ex, true)
end
end # module AttributeHelper
end # module Trimble
#-------------------------------------------------------------------------------
file_loaded(__FILE__)
#-------------------------------------------------------------------------------
+285
View File
@@ -0,0 +1,285 @@
# Copyright 2014-2021, Trimble Inc.
#
# License: The MIT License (MIT)
require "sketchup.rb"
require "stringio"
module Trimble
module AttributeHelper
PLUGIN = self
class << self
attr_reader :app_observer
attr_reader :model_observer
attr_reader :selection_observer
end
def self.visualize_selected
content = self.traverse_selected
html = self.wrap_content(content)
options = {
:dialog_title => "Attribute Visualizer",
:preferences_key => 'AttributeVisualizer',
:scrollable => true,
:resizable => true,
:height => 300,
:width => 400,
:left => 200,
:top => 200
}
@window ||= UI::WebDialog.new(options)
@window.set_html(html)
@window.set_on_close {
@window = nil
self.detach_observers
}
unless @window.visible?
@window.show
self.attach_observers
end
end
def self.attach_observers
@app_observer ||= AppObserver.new
@model_observer ||= ModelObserver.new
@selection_observer ||= SelectionObserver.new
model = Sketchup.active_model
Sketchup.remove_observer(@app_observer)
model.remove_observer(@model_observer)
model.selection.remove_observer(@selection_observer)
Sketchup.add_observer(@app_observer)
model.add_observer(@model_observer)
model.selection.add_observer(@selection_observer)
end
def self.detach_observers
Sketchup.remove_observer(@app_observer)
Sketchup.active_model.remove_observer(@model_observer)
Sketchup.active_model.selection.remove_observer(@selection_observer)
end
def self.traverse_selected
html = StringIO.new
model = Sketchup.active_model
selection = model.selection
if selection.empty?
if model.active_path.nil?
entity = model
else
entity = model.active_path.last
end
else
return "Invalid selection size" unless selection.size == 1
entity = selection[0]
end
html.puts "<h1>#{self.escape_html(entity)}</h1>"
if entity.respond_to?(:name)
html.puts "<h2>#{self.escape_html(entity.name)}</h2>"
end
if entity.attribute_dictionaries
entity.attribute_dictionaries.each { |dictionary|
html.puts self.format_dictionary(dictionary)
}
else
html.puts "No dictionaries"
end
if entity.is_a?(Sketchup::Group)
definition = entity.entities.parent
elsif entity.is_a?(Sketchup::ComponentInstance)
definition = entity.definition
else
definition = nil
end
if definition && definition.attribute_dictionaries
html.puts "<h1>#{self.escape_html(definition)}</h1>"
html.puts "<h2>#{self.escape_html(definition.name)}</h2>"
definition.attribute_dictionaries.each { |dictionary|
html.puts self.format_dictionary(dictionary)
}
end
html.string
end
def self.format_dictionary(dictionary, path = "")
html_name = self.escape_html(dictionary.name)
path = "#{path}:#{html_name}"
html = StringIO.new
html.puts "<table>"
html.puts "<caption title='#{path}'>#{html_name}</caption>"
html.puts "<tbody>"
dictionary.each { |key, value|
html_key = self.escape_html(key)
html_value = self.escape_html(value)
node_path = "#{path}:#{html_key}"
html.puts "<tr title='#{node_path}'><td>#{html_key}</td><td>#{html_value}</td><td class='value_type'>#{value.class}</td></tr>"
}
if dictionary.attribute_dictionaries
dictionary.attribute_dictionaries.each { |sub_dic|
html.puts "<tr><td colspan='3' class='dictionary'>"
html.puts self.format_dictionary(sub_dic, path)
html.puts "</td></tr>"
}
end
html.puts "</tbody>"
html.puts "</table>"
html.string
end
def self.escape_html(data)
data.to_s.gsub("&", "&amp;").gsub("<", "&lt;").gsub(">", "&gt;")
end
def self.wrap_content(content)
html = <<-EOT
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<meta charset="UTF-8">
<style>
html {
font-family: "Calibri", sans-serif;
font-size: 10pt;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.2em;
}
table {
width: 100%;
/*padding: 0.5em;*/
border: 1px solid #666;
}
caption {
font-weight: bold;
text-align: left;
/*border-bottom: 1px solid silver;*/
padding: 0.2em;
}
td {
background: #f3f3f3;
padding: 0.2em;
}
td.dictionary {
background: none;
padding-left: 1em;
}
tr:hover td {
background: rgba(255,210,180,0.2);
}
.value_type {
text-align: right;
width: 5%;
}
</style>
<head>
<body>
#{content}
</body>
</html>
EOT
end
class SelectionObserver < Sketchup::SelectionObserver
def onSelectionAdded(selection, element)
selection_changed()
end
def onSelectionBulkChange(selection)
selection_changed()
end
def onSelectionCleared(selection)
selection_changed()
end
def onSelectionRemoved(selection, element)
selection_changed()
end
private
def selection_changed
PLUGIN.visualize_selected
end
end # class SelectionObserver
class ModelObserver < Sketchup::ModelObserver
def onActivePathChanged(model)
PLUGIN.visualize_selected
end
def onTransactionCommit(model)
model_changed(model)
end
def onTransactionEmpty(model)
model_changed(model)
end
def onTransactionRedo(model)
model_changed(model)
end
def onTransactionUndo(model)
model_changed(model)
end
private
def model_changed(model)
if @timer.nil?
@timer = UI.start_timer(0.0, false) {
@timer = nil
PLUGIN.visualize_selected
}
end
end
end # class ModelObserver
class AppObserver < Sketchup::AppObserver
def onNewModel(model)
observe_model(model)
end
def onOpenModel(model)
observe_model(model)
end
private
def observe_model(model)
model.add_observer(PLUGIN.model_observer)
model.selection.add_observer(PLUGIN.selection_observer)
PLUGIN.visualize_selected
end
end # class AppObserver
unless file_loaded?(__FILE__)
command = UI::Command.new("Attribute Helper") { self.visualize_selected }
command.status_bar_text = "Inspect and edit the attributes of a selection."
menu_name = Sketchup.version.to_f < 21.1 ? 'Plugins' : 'Developer'
menu = UI.menu(menu_name)
menu.add_item(command)
file_loaded(__FILE__)
end
end # module AttributeHelper
end # module Sketchup
+2
View File
@@ -0,0 +1,2 @@
ID=f4d9d053-4479-4a9a-90da-b79fa16e28c4
VERSION_ID=b787af5e-8e8e-4932-92ef-a3c99681795d
Binary file not shown.
+3 -2
View File
@@ -25,7 +25,8 @@ def patch_installer(tag):
with open(iss_file, "r") as file:
lines = file.readlines()
lines.insert(11, f'#define AppVersion "{tag}"\n')
lines.insert(11, f'#define AppVersion "{tag.split("-")[0]}"\n')
lines.insert(12, f'#define AppInfoVersion "{tag}"\n')
with open(iss_file, "w") as file:
file.writelines(lines)
@@ -39,7 +40,7 @@ def main():
return
tag = sys.argv[1]
if not re.match(r"[0-9]+(\.[0-9]+)*$", tag):
if not re.match(r"([0-9]+)\.([0-9]+)\.([0-9]+)", tag):
raise ValueError(f"Invalid tag provided: {tag}")
print(f"Patching version: {tag}")
+27 -33
View File
@@ -1,45 +1,39 @@
require "sketchup"
# frozen_string_literal: true
require "extensions"
require 'sketchup'
require 'extensions'
module SpeckleSystems
module SpeckleConnector
# Version - patched by CI
CONNECTOR_VERSION = "0.0.0"
# Speckle connector module to enable multiplayer mode ON!
module SpeckleConnector
# Version - patched by CI
CONNECTOR_VERSION = '0.0.0'
file = __FILE__.dup
# Account for Ruby encoding bug under Windows.
file.force_encoding("UTF-8") if file.respond_to?(:force_encoding)
# Support folder should be named the same as the root .rb file.
folder_name = File.basename(file, ".*")
file = __FILE__.dup
# Path to the root .rb file (this file).
PATH_ROOT = File.dirname(file).freeze
# Account for Ruby encoding bug under Windows.
file.force_encoding('UTF-8') if file.respond_to?(:force_encoding)
# Path to the support folder.
PATH = File.join(PATH_ROOT, folder_name).freeze
# Support folder should be named the same as the root .rb file.
folder_name = File.basename(file, '.*')
# Run from localhost or from build files
DEV_MODE = false
puts("Loading Speckle Connector v#{CONNECTOR_VERSION} from #{DEV_MODE ? 'dev' : 'build'}")
# Path to the root .rb file (this file).
PATH_ROOT = File.dirname(file).freeze
# Path to the support folder.
PATH = File.join(PATH_ROOT, folder_name).freeze
unless file_loaded?(__FILE__)
# Run from localhost or from build files
DEV_MODE = false
puts("Loading Speckle Connector v#{CONNECTOR_VERSION} from #{DEV_MODE ? 'dev' : 'build'}")
ex = SketchupExtension.new("Speckle SketchUp", File.join(PATH, "main"))
unless file_loaded?(__FILE__)
ex = SketchupExtension.new('Speckle SketchUp', File.join(PATH, 'bootstrap'))
ex.description = 'Speckle Connector for SketchUp'
ex.version = CONNECTOR_VERSION
ex.copyright = 'AEC Systems Ltd.'
ex.creator = 'Speckle Systems'
Sketchup.register_extension(ex, true)
ex.description = "Speckle Connector for SketchUp"
ex.version = CONNECTOR_VERSION
ex.copyright = "AEC Systems Ltd."
ex.creator = "Speckle Systems"
Sketchup.register_extension(ex, true)
file_loaded(__FILE__)
end
file_loaded(__FILE__)
end
end
-57
View File
@@ -1,57 +0,0 @@
require "JSON"
begin
require("sqlite3")
rescue LoadError
# ty msp-greg! https://github.com/MSP-Greg/SUMisc/releases/tag/sqlite3-mingw-1
Gem.install(File.join(File.dirname(File.expand_path(__FILE__)), "utils/sqlite3-1.4.2.mspgreg-x64-mingw32.gem"))
require("sqlite3")
end
module SpeckleSystems::SpeckleConnector
module Accounts
def self.load_accounts
dir = _get_speckle_dir
db_path = File.join(dir, "Accounts.db")
unless File.exist?(db_path)
raise(IOError, "No Accounts db found. Please read the guide for different options for adding your account: \nhttps://speckle.guide/user/manager.html#adding-accounts")
end
db = SQLite3::Database.new(db_path)
rows = db.execute("SELECT * FROM objects")
db.close
rows.map { |row| JSON.parse(row[1]) }
end
def self.default_account
accts = load_accounts
accts.select { |acc| acc["isDefault"] }[0] || accts[0]
end
def self.get_suuid
dir = _get_speckle_dir
suuid_path = File.join(dir, "suuid")
return unless File.exist?(suuid_path)
File.read(suuid_path)
end
def self._get_speckle_dir
speckle_dir =
case Sketchup.platform
# sometimes Dir.home on windows points somewhere else bc I guess it's picking up a higher level user?
when :platform_win then File.join(Dir.pwd[%r{^((?:[^/]*/){3})}], "AppData/Roaming/Speckle")
when :platform_osx then File.join(Dir.home, ".config", "Speckle")
else
nil
end
return speckle_dir if Dir.exist?(speckle_dir)
raise(
IOError,
"No Speckle Directory exists. Please read the guide to get Speckle set up on your machine: \nhttps://speckle.guide/user/manager.html"
)
end
end
end
+36
View File
@@ -0,0 +1,36 @@
# frozen_string_literal: true
require 'sketchup'
require 'pathname'
require 'speckle_connector/debug'
require_relative 'src/ui/sketchup_ui'
require_relative 'src/ui/ui_controller'
require_relative 'src/commands/menu_command_handler'
require_relative 'src/app/speckle_connector_app'
require_relative 'src/states/user_state'
require_relative 'src/states/initial_state'
require_relative 'src/commands/speckle_menu_commands'
# Speckle Connector on SketchUp to enable Multiplayer mode ON!
module SpeckleConnector
SKETCHUP_VERSION = Sketchup.version.to_i
dir = __dir__.dup
dir.force_encoding('UTF-8') if dir.respond_to?(:force_encoding)
SPECKLE_CONNECTOR_SRC_PATH = Pathname.new(File.expand_path(dir)).cleanpath.to_s
def self.initialize_app
sketchup_ui = Ui::SketchupUi.new
ui_controller = Ui::UiController.new(sketchup_ui)
menu_commands = Commands::MenuCommandHandler.new
user_state = SpeckleConnector::States::UserState.new({})
initial_state = SpeckleConnector::States::InitialState.new(user_state)
app = SpeckleConnector::App::SpeckleConnectorApp.new(menu_commands, initial_state, ui_controller)
# Add menu commands to SketchUp and Speckle application
Commands::SpeckleMenuCommands.add_initial_commands!(app)
app
end
app = initialize_app
SPECKLE_APP = app
end
@@ -1,32 +0,0 @@
require "sketchup"
require "speckle_connector/converter/to_speckle"
require "speckle_connector/converter/to_native"
module SpeckleSystems::SpeckleConnector
SKETCHUP_UNIT_STRINGS = { "m" => "m", "mm" => "mm", "ft" => "feet", "in" => "inch", "yd" => "yard", "cm" => "cm" }.freeze
public_constant :SKETCHUP_UNIT_STRINGS
class ConverterSketchup
include ToNative
include ToSpeckle
attr_accessor :units, :component_defs, :registry, :entity_observer
def initialize(units = "m")
@units = units
@component_defs = {}
# @registry = Sketchup.active_model.attribute_dictionary("speckle_id_registry", true)
# @entity_observer = SpeckleEntityObserver.new
end
def convert_to_speckle(obj)
case obj.typename
when "Edge" then edge_to_speckle(obj)
when "Face" then face_to_speckle(obj)
when "Group" then component_instance_to_speckle(obj, is_group: true)
when "ComponentDefinition" then component_definition_to_speckle(obj)
when "ComponentInstance" then component_instance_to_speckle(obj)
else nil
end
end
end
end
-21
View File
@@ -1,21 +0,0 @@
module SpeckleSystems::SpeckleConnector
class SpeckleEntityObserver < Sketchup::EntityObserver
attr_accessor :registry
def initialize
super()
@registry = Sketchup.active_model.attribute_dictionary("speckle_id_registry", true)
end
def onEraseEntity(entity)
app_id = entity.get_attribute("speckle", "applicationId")
return if app_id.nil?
p(app_id)
@registry.delete_key(app_id)
p(@registry)
end
end
end
-243
View File
@@ -1,243 +0,0 @@
require "sketchup"
# To Native conversions for the ConverterSketchup
module SpeckleSystems::SpeckleConnector::ToNative
def traverse_commit_object(obj)
if can_convert_to_native(obj)
convert_to_native(obj, Sketchup.active_model.entities)
elsif obj.is_a?(Hash) && obj.key?("speckle_type")
return if is_ignored_speckle_type(obj)
puts(">>> Found #{obj["speckle_type"]}: #{obj["id"]}")
props = obj.keys.filter_map { |key| key unless key.start_with?("_") }
props.each { |prop| traverse_commit_object(obj[prop]) }
elsif obj.is_a?(Hash)
obj.each_value { |value| traverse_commit_object(value) }
elsif obj.is_a?(Array)
obj.each { |value| traverse_commit_object(value) }
else
nil
end
end
def can_convert_to_native(obj)
return false unless obj.is_a?(Hash) && obj.key?("speckle_type")
[
"Objects.Geometry.Line",
"Objects.Geometry.Polyline",
"Objects.Geometry.Mesh",
"Objects.Geometry.Brep",
"Objects.Other.BlockInstance",
"Objects.Other.BlockDefinition",
"Objects.Other.RenderMaterial"
].include?(obj["speckle_type"])
end
def is_ignored_speckle_type(obj)
["Objects.BuiltElements.Revit.Parameter"].include?(obj["speckle_type"])
end
def convert_to_native(obj, entities = Sketchup.active_model.entities)
puts(">>> Converting #{obj["speckle_type"]}: #{obj["id"]}")
case obj["speckle_type"]
when "Objects.Geometry.Line", "Objects.Geometry.Polyline" then if entities == Sketchup.active_model.entities
edge_to_native_component(obj, entities)
else
edge_to_native(obj, entities)
end
when "Objects.Other.BlockInstance" then component_instance_to_native(obj, entities)
when "Objects.Other.BlockDefinition" then component_definition_to_native(obj)
when "Objects.Geometry.Mesh" then if entities == Sketchup.active_model.entities
mesh_to_native_component(obj, entities)
else
mesh_to_native(obj, entities)
end
when "Objects.Geometry.Brep" then if entities == Sketchup.active_model.entities
mesh_to_native_component(obj["displayMesh"], entities)
else
mesh_to_native(obj["displayMesh"], entities)
end
when obj.key?["displayValue"]
parent_id = obj["applicationId"] || obj["id"]
obj["displayValue"].each do |o|
o["applicationId"] = "#{parent_id}::#{o["id"]}" if o["applicationId"].nil?
convert_to_native(o, entities)
end
else
nil
end
rescue StandardError => e
puts("Failed to convert #{obj["speckle_type"]} (id: #{obj["id"]})")
puts(e)
nil
end
# def register_receive(entity, id)
# # entity.add_observer(@entity_observer)
# # entity.set_attribute("speckle", "applicationId", id)
# @registry[id] = entity.persistent_id
# end
# def get_received_entity(app_id)
# return if @registry[app_id].nil?
# end
# def received?(id)
# !@registry[id].nil?
# end
def length_to_native(length, units = @units)
length.__send__(SpeckleSystems::SpeckleConnector::SKETCHUP_UNIT_STRINGS[units])
end
def edge_to_native(line, entities)
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"]) }
points.push(points[0]) if line["closed"]
entities.add_edges(*points)
else
start_pt = point_to_native(line["start"]["x"], line["start"]["y"], line["start"]["z"], line["units"])
end_pt = point_to_native(line["end"]["x"], line["end"]["y"], line["end"]["z"], line["units"])
entities.add_edges(start_pt, end_pt)
end
end
def edge_to_native_component(line, entities)
line_id = line["applicationId"] || line["id"]
definition = component_definition_to_native([line], "def::#{line_id}")
find_and_erase_existing_instance(definition, line_id)
instance = entities.add_instance(definition, Geom::Transformation.new)
instance.name = line_id
instance
end
def face_to_native
nil
end
def point_to_native(x, y, z, units)
Geom::Point3d.new(length_to_native(x, units), length_to_native(y, units), length_to_native(z, units))
end
# converts a mesh to a native mesh and adds the faces to the given entities collection
def mesh_to_native(mesh, entities)
native_mesh = Geom::PolygonMesh.new(mesh["vertices"].count / 3)
points = []
mesh["vertices"].each_slice(3) do |pt|
points.push(point_to_native(pt[0], pt[1], pt[2], mesh["units"]))
end
faces = mesh["faces"]
while faces.count.positive?
num_pts = faces.shift
# 0 -> 3, 1 -> 4 to preserve backwards compatibility
num_pts += 3 if num_pts < 3
indices = faces.shift(num_pts)
native_mesh.add_polygon(indices.map { |index| points[index] })
end
entities.add_faces_from_mesh(native_mesh, 4, material_to_native(mesh["renderMaterial"]))
native_mesh
end
# creates a component definition and instance from a mesh
def mesh_to_native_component(mesh, entities)
mesh_id = mesh["applicationId"] || mesh["id"]
definition = component_definition_to_native([mesh], "def::#{mesh_id}")
find_and_erase_existing_instance(definition, mesh_id)
instance = entities.add_instance(definition, Geom::Transformation.new)
instance.name = mesh_id
instance.material = material_to_native(mesh["renderMaterial"])
instance
end
# finds or creates a component definition from the geometry and the given name
def component_definition_to_native(geometry, name, application_id = "")
definition = Sketchup.active_model.definitions[name]
return definition if definition && (definition.name == name || definition.guid == application_id)
definition&.entities&.clear!
definition ||= Sketchup.active_model.definitions.add(name)
geometry.each { |obj| convert_to_native(obj, definition.entities) }
puts("definition finished: #{name} (#{application_id})")
puts(" entity count: #{definition.entities.count}")
definition
end
# takes a component definition and finds and erases the first instance with the matching name (and optionally the applicationId)
def find_and_erase_existing_instance(definition, name, app_id = "")
definition.instances.find { |ins| ins.name == name || ins.guid == app_id }&.erase!
end
# creates a component instance from a block
def component_instance_to_native(block, entities)
# is_group = block.key?("is_sketchup_group") && block["is_sketchup_group"]
# something about this conversion is freaking out if nested block geo is a group
# so this is set to false always until I can figure this out
is_group = false
definition = component_definition_to_native(
block["blockDefinition"]["geometry"],
block["blockDefinition"]["name"],
block["blockDefinition"]["applicationId"]
)
name = block["name"].nil? || block["name"].empty? ? block["id"] : block["name"]
find_and_erase_existing_instance(definition, name, block["applicationId"])
transform = transform_to_native(
block["transform"].is_a?(Hash) ? block["transform"]["value"] : block["transform"],
block["units"]
)
instance =
if is_group
entities.add_group(definition.entities.to_a)
else
entities.add_instance(definition, transform)
end
puts("Failed to create instance for speckle block instance #{block["id"]}") if instance.nil?
instance.transformation = transform if is_group
instance.material = material_to_native(block["renderMaterial"])
instance.name = name
instance
end
def transform_to_native(t_arr, units = @units)
Geom::Transformation.new(
[
t_arr[0],
t_arr[4],
t_arr[8],
t_arr[12],
t_arr[1],
t_arr[5],
t_arr[9],
t_arr[13],
t_arr[2],
t_arr[6],
t_arr[10],
t_arr[14],
length_to_native(t_arr[3], units),
length_to_native(t_arr[7], units),
length_to_native(t_arr[11], units),
t_arr[15]
]
)
end
def material_to_native(render_mat)
return if render_mat.nil?
# return material with same name if it exists
name = render_mat["name"] || render_mat["id"]
material = Sketchup.active_model.materials[name]
return material if material
# create a new sketchup material
material = Sketchup.active_model.materials.add(name)
material.alpha = render_mat["opacity"]
argb = render_mat["diffuse"]
material.color = Sketchup::Color.new((argb >> 16) & 255, (argb >> 8) & 255, argb & 255, (argb >> 24) & 255)
material
end
end
-270
View File
@@ -1,270 +0,0 @@
require "sketchup"
# To Speckle conversions for the ConverterSketchup
module SpeckleSystems::SpeckleConnector::ToSpeckle
def length_to_speckle(length)
length.__send__("to_#{SpeckleSystems::SpeckleConnector::SKETCHUP_UNIT_STRINGS[@units]}")
end
# convert an edge to a speckle line
def edge_to_speckle(edge)
{
speckle_type: "Objects.Geometry.Line",
applicationId: edge.persistent_id.to_s,
units: @units,
start: vertex_to_speckle(edge.start),
end: vertex_to_speckle(edge.end),
domain: speckle_interval(0, Float(edge.length)),
bbox: bounds_to_speckle(edge.bounds)
}
end
# covnert a component definition to a speckle block definition
def component_definition_to_speckle(definition)
guid = definition.guid
return @component_defs[guid] if @component_defs.key?(guid)
speckle_def = {
speckle_type: "Objects.Other.BlockDefinition",
applicationId: guid,
units: @units,
name: definition.name,
# i think the base point is always the origin?
basePoint: speckle_point,
"@geometry" => if %w[Edge Face].include?(definition.entities[0].typename)
group_mesh_to_speckle(definition)
else
definition.entities.map { |entity| convert_to_speckle(entity) }
end
}
@component_defs[guid] = speckle_def
end
# convert a component instane to a speckle block instance
def component_instance_to_speckle(instance, is_group: false)
transform = instance.transformation
{
speckle_type: "Objects.Other.BlockInstance",
applicationId: instance.guid,
is_sketchup_group: is_group,
units: @units,
bbox: bounds_to_speckle(instance.bounds),
name: instance.name == "" ? nil : instance.name,
renderMaterial: instance.material.nil? ? nil : material_to_speckle(instance.material),
transform: transform_to_speckle(transform),
"@blockDefinition" => component_definition_to_speckle(instance.definition)
}
end
def group_mesh_to_speckle(component_def)
mat_groups = {}
nested_blocks = []
lines = []
component_def.entities.each do |entity|
nested_blocks.push(component_instance_to_speckle(entity)) if entity.typename == "ComponentInstance"
next unless %w[Edge Face].include?(entity.typename)
if entity.typename == "Edge"
lines.push(edge_to_speckle(entity))
else
face = entity
# convert material
mat_id = face.material.nil? ? "none" : face.material.entityID
mat_groups[mat_id] = initialise_group_mesh(face, component_def.bounds) unless mat_groups.key?(mat_id)
if face.loops.size > 1
mesh = face.mesh
mat_groups[mat_id]["@(31250)vertices"].push(*mesh_points_to_array(mesh))
mat_groups[mat_id]["@(62500)faces"].push(*mesh_faces_to_array(mesh, mat_groups[mat_id][:pt_count] - 1))
else
mat_groups[mat_id]["@(31250)vertices"].push(*face_vertices_to_array(face))
mat_groups[mat_id]["@(62500)faces"].push(*face_indices_to_array(face, mat_groups[mat_id][:pt_count]))
end
mat_groups[mat_id][:pt_count] += face.vertices.count
end
end
mat_groups.values.map { |group| group.delete(:pt_count) }
mat_groups.values + lines + nested_blocks
end
def transform_to_speckle(transform)
t_arr = transform.to_a
{
speckle_type: "Objects.Other.Transform",
units: @units,
value: [
t_arr[0],
t_arr[4],
t_arr[8],
length_to_speckle(t_arr[12]),
t_arr[1],
t_arr[5],
t_arr[9],
length_to_speckle(t_arr[13]),
t_arr[2],
t_arr[6],
t_arr[10],
length_to_speckle(t_arr[14]),
t_arr[3],
t_arr[7],
t_arr[11],
t_arr[15]
]
}
end
def initialise_group_mesh(face, bounds)
{
speckle_type: "Objects.Geometry.Mesh",
units: @units,
bbox: bounds_to_speckle(bounds),
"@(31250)vertices" => [],
"@(62500)faces" => [],
"@(31250)textureCoordinates" => [],
pt_count: 0,
renderMaterial: face.material.nil? ? nil : material_to_speckle(face.material)
}
end
# get an array of face indices from a sketchup polygon mesh
def mesh_faces_to_array(mesh, offset)
faces = []
puts(faces)
mesh.polygons.each do |poly|
faces.push(
poly.count, *poly.map { |index| index.abs + offset }
)
end
faces
end
# get a flat array of vertices from a sketchup polygon mesh
def mesh_points_to_array(mesh)
pts_array = []
mesh.points.each do |pt|
pts_array.push(
length_to_speckle(pt[0]),
length_to_speckle(pt[1]),
length_to_speckle(pt[2])
)
end
pts_array
end
# get a flat array of face indices from a sketchup face
def face_indices_to_array(face, offset)
face_array = [face.vertices.count]
face_array.push(*face.vertices.count.times.map { |index| index + offset })
face_array
end
# get a flat array of vertices from a list of sketchup vertices
def face_vertices_to_array(face)
pts_array = []
face.vertices.each do |v|
pt = v.position
pts_array.push(length_to_speckle(pt[0]), length_to_speckle(pt[1]), length_to_speckle(pt[2]))
end
pts_array
end
def uvs_to_array(mesh)
uvs_array = []
mesh.uvs(true).each do |pt|
uvs_array.push(
length_to_speckle(pt[0] / pt[2]),
length_to_speckle(pt[1] / pt[2])
)
end
uvs_array
end
def face_to_speckle(face)
mesh = face.loops.count > 1 ? face.mesh : nil
{
speckle_type: "Objects.Geometry.Mesh",
units: @units,
renderMaterial: face.material.nil? ? nil : material_to_speckle(face.material),
bbox: bounds_to_speckle(face.bounds),
"@(31250)vertices" => mesh.nil? ? face_vertices_to_array(face) : mesh_points_to_array(mesh),
"@(62500)faces" => mesh.nil? ? face_indices_to_array(face, 0) : mesh_faces_to_array(mesh, -1)
}
end
def vertex_to_speckle(vertex)
point = vertex.position
{
speckle_type: "Objects.Geometry.Point",
units: @units,
x: length_to_speckle(point[0]),
y: length_to_speckle(point[1]),
z: length_to_speckle(point[2])
}
end
def material_to_speckle(material)
rgba = material.color.to_a
{
speckle_type: "Objects.Other.RenderMaterial",
name: material.name,
diffuse: [rgba[3] << 24 | rgba[0] << 16 | rgba[1] << 8 | rgba[2]].pack("l").unpack1("l"),
opacity: material.alpha,
emissive: -16_777_216,
metalness: 0,
roughness: 1
}
end
def bounds_to_speckle(bounds)
min_pt = bounds.min
{
speckle_type: "Objects.Geometry.Box",
units: @units,
area: 0,
volume: 0,
xSize: speckle_interval(min_pt[0], bounds.width),
ySize: speckle_interval(min_pt[1], bounds.height),
zSize: speckle_interval(min_pt[2], bounds.depth),
basePlane: speckle_plane
}
end
def speckle_interval(start_val, end_val)
{
speckle_type: "Objects.Primitive.Interval",
units: @units,
start: start_val.is_a?(Length) ? length_to_speckle(start_val) : start_val,
end: end_val.is_a?(Length) ? length_to_speckle(end_val) : end_val
}
end
def speckle_point(x = 0.0, y = 0.0, z = 0.0, vector: false)
{
speckle_type: vector ? "Objects.Geometry.Vector" : "Objects.Geometry.Point",
units: @units,
x: x.is_a?(Length) ? length_to_speckle(x) : x,
y: y.is_a?(Length) ? length_to_speckle(y) : y,
z: z.is_a?(Length) ? length_to_speckle(z) : z
}
end
def speckle_vector(x = 0.0, y = 0.0, z = 0.0)
speckle_point(x, y, z, vector: true)
end
def speckle_plane(xdir: [1, 0, 0], ydir: [0, 1, 0], normal: [0, 0, 1], origin: [0, 0, 0])
{
speckle_type: "Objects.Geometry.Plane",
units: @units,
xdir: speckle_vector(*xdir),
ydir: speckle_vector(*ydir),
normal: speckle_vector(*normal),
origin: speckle_point(*origin)
}
end
end
+8 -3
View File
@@ -1,17 +1,22 @@
module SpeckleSystems::SpeckleConnector
# frozen_string_literal: true
# Speckle connector module to enable multiplayer mode ON!
module SpeckleConnector
# from thomthom
# https://github.com/thomthom/true-bend/blob/master/src/tt_truebend/debug.rb
# @note Debug method to reload the plugin.
#
# @example
# SpeckleSystems::SpeckleConnector.reload
# SpeckleConnector.reload
#
# @return [Integer] Number of files reloaded.
# rubocop:disable SketchupSuggestions/FileEncoding
def self.reload
load(__FILE__)
pattern = File.join(__dir__, "**/*.rb")
pattern = File.join(__dir__, '**/*.rb')
Dir.glob(pattern).each { |file| load(file) }
.size
end
# rubocop:enable SketchupSuggestions/FileEncoding
end
-204
View File
@@ -1,204 +0,0 @@
require "JSON"
require "json"
require "sketchup"
require "speckle_connector/converter/converter_sketchup"
require "speckle_connector/accounts"
module SpeckleSystems::SpeckleConnector
UNITS = { 0 => "in", 1 => "ft", 2 => "mm", 3 => "cm", 4 => "m", 5 => "yd" }.freeze
public_constant :UNITS
@to_send = {}
@connected = false
def self.queue_send(stream_id, converted)
@to_send = { stream_id: stream_id, converted: converted }
send_from_queue(stream_id) if @connected
end
def self.send_from_queue(stream_id)
return unless @to_send[:stream_id] == stream_id
@dialog.execute_script("convertedFromSketchup('#{@to_send[:stream_id]}',#{@to_send[:converted].to_json})")
@dialog.execute_script("oneClickSend('#{@to_send[:stream_id]}')")
@to_send = {}
end
def self.init_dialog
options = {
dialog_title: "SpeckleSketchUp",
preferences_key: "example.htmldialog.materialinspector",
style: UI::HtmlDialog::STYLE_DIALOG,
min_width: 250,
min_height: 50
}
dialog = UI::HtmlDialog.new(options)
dialog.center
dialog
end
def self.create_dialog(show: true)
if @dialog&.visible?
@dialog.bring_to_front
else
@dialog ||= init_dialog
@dialog.add_action_callback("send_selection") do |_action_context, stream_id|
send_selection(stream_id)
nil
end
@dialog.add_action_callback("receive_objects") do |_action_context, base, stream_id|
receive_objects(base, stream_id)
nil
end
@dialog.add_action_callback("reload_accounts") do |_action_context|
reload_accounts
end
@dialog.add_action_callback("init_local_accounts") do |_action_context|
init_local_accounts
end
@dialog.add_action_callback("load_saved_streams") do |_action_context|
load_saved_streams
end
@dialog.add_action_callback("save_stream") do |_action_context, stream_id|
save_stream(stream_id)
end
@dialog.add_action_callback("remove_stream") do |_action_context, stream_id|
remove_stream(stream_id)
end
@dialog.add_action_callback("notify_connected") do |_action_context, stream_id|
notify_connected(stream_id)
end
# set connected to false when dialog is closed
@dialog.set_can_close do
@connected = false
!@connected
end
if DEV_MODE
puts("Launching Speckle Connector from http://localhost:8081")
@dialog.set_url("http://localhost:8081")
else
html_file = File.join(File.dirname(File.expand_path(__FILE__)), "html", "index.html")
puts("Launching Speckle Connector from #{html_file}")
@dialog.set_file(html_file)
end
@dialog.show if show
end
@dialog
end
def self.notify_connected(stream_id)
@connected = true
send_from_queue(stream_id)
end
def self.convert_to_speckle(to_convert)
converter = ConverterSketchup.new(UNITS[Sketchup.active_model.options["UnitsOptions"]["LengthUnit"]])
to_convert.map { |entity| converter.convert_to_speckle(entity) }
end
def self.send_selection(stream_id)
converted = convert_to_speckle(Sketchup.active_model.selection)
puts("converted #{converted.count} objects for stream #{stream_id}")
# puts(converted.to_json)
@dialog.execute_script("convertedFromSketchup('#{stream_id}',#{converted.to_json})")
rescue StandardError => e
puts(e)
@dialog.execute_script("sketchupOperationFailed('#{stream_id}')")
end
def self.receive_objects(base, stream_id)
puts("received objects from stream #{stream_id}")
model = Sketchup.active_model
converter = ConverterSketchup.new(UNITS[model.options["UnitsOptions"]["LengthUnit"]])
converter.traverse_commit_object(base)
@dialog.execute_script("finishedReceiveInSketchup('#{stream_id}')")
rescue StandardError => e
puts(e)
@dialog.execute_script("sketchupOperationFailed('#{stream_id}')")
end
def self.one_click_send
acct = Accounts.default_account
return puts("No local account found. Please refer to speckle.guide for more information.") if acct.nil?
create_dialog
to_convert = Sketchup.active_model.selection.count > 0 ? Sketchup.active_model.selection : Sketchup.active_model.entities
if first_saved_stream.nil?
create_stream(to_convert)
else
queue_send(first_saved_stream, convert_to_speckle(to_convert))
end
rescue StandardError => e
puts(e)
@dialog.execute_script("sketchupOperationFailed('#{@to_send[:stream_id]}')")
end
def self.first_saved_stream
saved_streams = Sketchup.active_model.attribute_dictionary("speckle", true)["streams"] or []
saved_streams.nil? || saved_streams.empty? ? nil : saved_streams[0]
end
def self.load_saved_streams
saved_streams = Sketchup.active_model.attribute_dictionary("speckle", true)["streams"] or []
@dialog.execute_script("setSavedStreams(#{saved_streams})")
end
def self.init_local_accounts
puts("Initialisation of Speckle accounts requested by plugin")
@dialog.execute_script("loadAccounts(#{Accounts.load_accounts.to_json}, #{Accounts.get_suuid.to_json})")
end
def self.reload_accounts
puts("Reload of Speckle accounts requested by plugin")
@dialog.execute_script("loadAccounts(#{Accounts.load_accounts.to_json})")
load_saved_streams
end
def self.save_stream(stream_id)
speckle_dict = Sketchup.active_model.attribute_dictionary("speckle", true)
saved = speckle_dict["streams"] || []
saved = saved.empty? ? [stream_id] : saved.unshift(stream_id)
speckle_dict["streams"] = saved
load_saved_streams
end
def self.remove_stream(stream_id)
speckle_dict = Sketchup.active_model.attribute_dictionary("speckle", true)
saved = speckle_dict["streams"] || []
saved -= [stream_id]
speckle_dict["streams"] = saved
load_saved_streams
end
def self.create_stream(to_convert)
acct = Accounts.default_account
return if acct.nil?
path = Sketchup.active_model.path
stream_name = path ? File.basename(path, ".*") : "Untitled SketchUp Model"
query = "mutation streamCreate($stream: StreamCreateInput!) {streamCreate(stream: $stream)}"
vars = { stream: { name: stream_name, description: "Stream created from SketchUp" } }
request = Sketchup::Http::Request.new("#{acct["serverInfo"]["url"]}/graphql", Sketchup::Http::POST)
request.headers = { "Authorization" => "Bearer #{acct["token"]}", "Content-Type" => "application/json" }
request.body = { query: query, variables: vars }.to_json
request.start do |_req, res|
res_data = JSON.parse(res.body)["data"]
raise(StandardError) unless res_data
stream_id = res_data["streamCreate"]
save_stream(stream_id)
queue_send(stream_id, convert_to_speckle(to_convert))
# send_selection(stream_id)
end
load_saved_streams
rescue StandardError => e
puts(e)
puts("Could not create a new stream")
end
end

Before

Width:  |  Height:  |  Size: 798 B

After

Width:  |  Height:  |  Size: 798 B

Before

Width:  |  Height:  |  Size: 1.8 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 665 B

After

Width:  |  Height:  |  Size: 665 B

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

Before

Width:  |  Height:  |  Size: 639 B

After

Width:  |  Height:  |  Size: 639 B

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

-37
View File
@@ -1,37 +0,0 @@
# frozen_string_literal: true
require "sketchup"
require "speckle_connector/dialog"
require "speckle_connector/debug"
module SpeckleSystems
module SpeckleConnector
unless file_loaded?(__FILE__)
cmd_cube = UI::Command.new("Dialog") { create_dialog }
cmd_cube.tooltip = "Launch Connector"
cmd_cube.status_bar_text = "Opens the Speckle Connector window"
cmd_cube.small_icon = "icons/s2logo.png"
cmd_cube.large_icon = "icons/s2logo.png"
menu = UI.menu("Tools")
menu.add_item(cmd_cube)
cmd_send = UI::Command.new("Send") { one_click_send }
cmd_send.tooltip = "Send to Speckle"
cmd_send.status_bar_text = "Send to Speckle"
cmd_send.small_icon = "icons/Sender.png"
cmd_send.large_icon = "icons/Sender.png"
menu = UI.menu("Tools")
menu.add_item(cmd_send)
toolbar = UI::Toolbar.new("Speckle")
toolbar.add_item(cmd_cube)
toolbar.add_item(cmd_send)
toolbar.restore
file_loaded(__FILE__)
end
end
end
@@ -0,0 +1,31 @@
# frozen_string_literal: true
require 'JSON'
require_relative '../ext/sqlite3'
require_relative '../constants/path_constants'
module SpeckleConnector
# Accounts to communicate with models on user's account.
module Accounts
def self.load_accounts
db_path = SPECKLE_ACCOUNTS_DB_PATH
unless File.exist?(db_path)
raise(
IOError,
"No Accounts db found. Please read the guide for different options for adding your account:\n
https://speckle.guide/user/manager.html#adding-accounts"
)
end
db = Sqlite3::Database.new(db_path)
rows = db.exec('SELECT * FROM objects')
db.close
rows.map { |row| JSON.parse(row[1]) }
end
def self.default_account
accounts = load_accounts
accounts.select { |acc| acc['isDefault'] }[0] || accounts[0]
end
end
end
+15
View File
@@ -0,0 +1,15 @@
# frozen_string_literal: true
module SpeckleConnector
module Actions
# State changer object.
class Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @param parameters [Array] parameters that the action takes
# @return [States::State] the new updated state object
def self.update_state(_state, *_parameters)
raise NotImplementedError, 'Implement in subclass.'
end
end
end
end
@@ -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,17 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Clear queue from state.
class ClearQueue < 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_speckle_state = state.speckle_state.with(:@message_queue => {})
state.with(:@speckle_state => new_speckle_state)
end
end
end
end
@@ -0,0 +1,18 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../ext/sqlite3'
require_relative '../constants/path_constants'
module SpeckleConnector
module Actions
# Action to collect preferences from database to UI.
class CollectPreferences < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
state.with_add_queue('collectPreferences', state.user_state.preferences.to_json, [])
end
end
end
end
@@ -0,0 +1,20 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Action to collect versions from sketchup and connector to track user's version by mixpanel.
class CollectVersions < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
versions = {
sketchup: Sketchup.version.to_i,
speckle: SpeckleConnector::CONNECTOR_VERSION
}
state.with_add_queue('collectVersions', versions.to_json, [])
end
end
end
end
@@ -0,0 +1,15 @@
# frozen_string_literal: true
module SpeckleConnector
module Actions
# Action to update connected state of application.
class Connected < 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)
puts 'Speckle connected!'
state.with(:@connected => true)
end
end
end
end
@@ -0,0 +1,66 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../accounts/accounts'
require_relative '../actions/save_stream'
require_relative '../actions/queue_send'
require_relative '../convertors/converter'
module SpeckleConnector
module Actions
# Create stream.
class CreateStream < Action
def initialize(stream_name: nil)
super()
@stream_name = stream_name
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
# rubocop:disable Metrics/MethodLength
def update_state(state)
puts 'send to speckle'
acct = Accounts.default_account
if acct.nil?
puts 'No local account found. Please refer to speckle.guide for more information.'
return state
end
sketchup_model = state.sketchup_state.sketchup_model
path = sketchup_model.path
if @stream_name.nil?
@stream_name = path ? File.basename(path, '.*') : 'Untitled SketchUp Model'
end
query = 'mutation streamCreate($stream: StreamCreateInput!) {streamCreate(stream: $stream)}'
vars = { stream: { name: @stream_name, description: 'Stream created from SketchUp' } }
request = Sketchup::Http::Request.new("#{acct['serverInfo']['url']}/graphql", Sketchup::Http::POST)
request.headers = { 'Authorization' => "Bearer #{acct['token']}", 'Content-Type' => 'application/json' }
request.body = { query: query, variables: vars }.to_json
to_convert = if sketchup_model.selection.count > 0
sketchup_model.selection
else
sketchup_model.entities
end
state = evaluate_request(sketchup_model, request, state, to_convert)
Actions::LoadSavedStreams.update_state(state, {})
end
# rubocop:enable Metrics/MethodLength
private
def evaluate_request(sketchup_model, request, state, to_convert)
converter = Converters::Converter.new(sketchup_model)
request.start do |_req, res|
res_data = JSON.parse(res.body)['data']
raise(StandardError) unless res_data
stream_id = res_data['streamCreate']
state = Actions::SaveStream.new(stream_id).update_state(state)
converted = to_convert.map { |entity| converter.convert_to_speckle(entity) }
state = Actions::QueueSend.new(stream_id, converted).update_state(state)
end
state
end
end
end
end
@@ -0,0 +1,26 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Deactivate diffing.
class DeactivateDiffing < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
puts 'Diffing deactivated!'
speckle_entities = state.speckle_state.speckle_entities
diffing_activated_speckle_entities = speckle_entities.reject do |_id, entity|
entity.active_diffing_stream_id.nil?
end
diffing_activated_speckle_entities.each do |id, entity|
new_entity = entity.deactivate_diffing
speckle_entities = speckle_entities.put(id, new_entity)
end
new_speckle_state = state.speckle_state.with_speckle_entities(speckle_entities)
state.with_speckle_state(new_speckle_state)
end
end
end
end
@@ -0,0 +1,49 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../load_sketchup_model'
module SpeckleConnector
module Actions
module Events
# Handle events that are triggered by the {AppObserver}.
class AppEventAction < EventAction
# Handle loading new or existing model
class OnNewOrChangedModel
# Handle events when the new or existing model is loaded in Sketchup
# @param state [States::State] the current state of speckle application
# @param event_data [Array<(Sketchup::Model)>] the event data for the given event. It consists of
# a double array with a single element that is the {Sketchup::Model} object of the loaded model.
def self.update_state(state, event_data)
return state unless event_data&.any?
model = event_data.flatten.first
Actions::LoadSketchupModel.update_state(state, model)
end
end
# Run actions that are needed before the sketchup quits
class OnQuit
# Handle when Sketchup application closes
# @param state [States::State] the current state of speckle application
# @param _event_data [Array] the event data
# @return [States::State] the transformed state object
def self.update_state(state, _event_data)
state
end
end
# Handlers that are used to handle specific events
ACTIONS = {
onNewModel: OnNewOrChangedModel,
onOpenModel: OnNewOrChangedModel,
onQuit: OnQuit
}.freeze
def self.actions
ACTIONS
end
end
end
end
end
@@ -0,0 +1,81 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../../sketchup_model/utils/face_utils'
require_relative '../../constants/dict_constants'
module SpeckleConnector
module Actions
module Events
# Event actions related to entities.
class EntitiesEventAction < EventAction
# Event action when element added.
class OnElementAdded
# @param state [States::State] the current state of the SpeckleConnector Application
def self.update_state(state, event_data)
modified_entities = event_data.to_a.collect { |e| e[1] }
# do not copy speckle base object specific attributes, because they are entity specific
modified_entities.each { |entity| entity.delete_attribute(SPECKLE_BASE_OBJECT) }
state
end
end
# Event action when element modified.
class OnElementModified
# @param state [States::State] the current state of the SpeckleConnector Application
def self.update_state(state, event_data)
speckle_state = state.speckle_state
modified_entity = event_data[0][1]
if modified_entity.is_a?(Sketchup::Face)
path = state.sketchup_state.sketchup_model.active_path
modified_faces = SketchupModel::Utils::FaceUtils.near_faces(modified_entity.edges)
path_objects = path.nil? ? [] : path + path.collect(&:definition)
parent_ids = path_objects.collect(&:persistent_id)
ids_to_invalidate = modified_faces.collect(&:persistent_id) + parent_ids
entities_to_invalidate = speckle_entities_to_invalidate(speckle_state, ids_to_invalidate)
new_speckle_state = invalidate_speckle_entities(speckle_state, entities_to_invalidate)
return state.with_speckle_state(new_speckle_state.with_invalid_streams_queue)
end
state
end
# @param speckle_state [States::SpeckleState] the current state of the Speckle
def self.speckle_entities_to_invalidate(speckle_state, ids)
speckle_state.speckle_entities.to_h.select { |id, _| ids.include?(id) }
end
# @param speckle_state [States::SpeckleState] the current state of the Speckle
def self.invalidate_speckle_entities(speckle_state, entities_to_invalidate)
speckle_entities = speckle_state.speckle_entities
entities_to_invalidate.each do |id, speckle_entity|
edited_speckle_entity = speckle_entity.with_invalid
speckle_entities = speckle_entities.put(id, edited_speckle_entity)
end
speckle_state.with_speckle_entities(speckle_entities)
end
end
# Event action when element removed.
class OnElementRemoved
# @param state [States::State] the current state of the SpeckleConnector Application
def self.update_state(state, _event_data)
# TODO: Do state updates when element removed
state
end
end
# Handlers that are used to handle specific events
ACTIONS = {
onElementRemoved: OnElementRemoved,
onElementAdded: OnElementAdded,
onElementModified: OnElementModified
}.freeze
def self.actions
ACTIONS
end
end
end
end
end
@@ -0,0 +1,34 @@
# frozen_string_literal: true
module SpeckleConnector
module Actions
# This module contains actions that are performed to handle events triggered by observers in Sketchup.
module Events
# Base action for Handling events
class EventAction
def self.actions
raise NoMethodError, 'Implement in a subclass'
end
# Handle the events that were collected by the observer. In case of the selection observer,
# we only need to handle the events once if any of the events actually happened.
# @param event_data [Hash{Symbol=>Array}] the event data grouped by the event name
# @param state [States::State] the current state of the SpeckleConnector Application
# @return [States::State] the transformed state
def self.update_state(state, events)
# Don't do anything if there are no events for this action
return state unless events
actions = self.actions
actions.each do |event_name, action|
next unless events.key?(event_name)
event_data = events[event_name]
state = action.update_state(state, event_data)
end
state
end
end
end
end
end
@@ -0,0 +1,41 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../load_sketchup_model'
module SpeckleConnector
module Actions
module Events
# Handle events that are triggered by the {ModelObserver}.
class ModelEventAction < EventAction
# Handle loading new or existing model
class OnActivePathChanged
# Handle events when the new or existing model is loaded in Sketchup
# @param state [States::State] the current state of speckle application
# @param event_data [Array<(Sketchup::Model)>] the event data for the given event. It consists of
# a double array with a single element that is the {Sketchup::Model} object of the loaded model.
def self.update_state(state, _event_data)
sketchup_state = state.sketchup_state
active_path = sketchup_state.sketchup_model.active_path
observers = state.speckle_state.observers
update_object_observers(active_path, observers)
return state
end
def self.update_object_observers(path, observers)
path[-1].definition.entities.add_observer(observers[ENTITIES_OBSERVER]) unless path.nil?
end
end
# Handlers that are used to handle specific events
ACTIONS = {
onActivePathChanged: OnActivePathChanged
}.freeze
def self.actions
ACTIONS
end
end
end
end
end
@@ -0,0 +1,20 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../accounts/accounts'
require_relative 'load_saved_streams'
module SpeckleConnector
module Actions
# Action to initialize local accounts from database.
class InitLocalAccounts < 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 'Initialisation of Speckle accounts requested by plugin'
accounts_data = state.speckle_state.accounts
state.with_add_queue('loadAccounts', accounts_data.to_json, [])
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
@@ -0,0 +1,34 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../states/state'
require_relative '../states/speckle_state'
require_relative '../states/sketchup_state'
require_relative '../accounts/accounts'
require_relative '../preferences/preferences'
require_relative '../constants/observer_constants'
module SpeckleConnector
module Actions
# Initialization of the real state of the speckle.
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, observers)
attach_app_observer!(observers[APP_OBSERVER])
accounts = SpeckleConnector::Accounts.load_accounts
speckle_state = States::SpeckleState.new(accounts, observers, {}, {})
# This should be the only point that `Sketchup_active_model` passed to application state.
sketchup_state = States::SketchupState.new(Sketchup.active_model)
preferences = Preferences.init_preferences(sketchup_state.sketchup_model)
user_state_with_preferences = state.user_state.with_preferences(preferences)
state = States::State.new(user_state_with_preferences, speckle_state, sketchup_state, false)
Actions::LoadSketchupModel.update_state(state, sketchup_state.sketchup_model)
end
def self.attach_app_observer!(observer)
Sketchup.add_observer(observer)
end
end
end
end
@@ -0,0 +1,18 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Action to load saved streams.
class LoadSavedStreams < 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)
(saved_streams = state.sketchup_state.sketchup_model
.attribute_dictionary('Speckle', true)['saved_streams']) or []
state.with_add_queue('setSavedStreams', saved_streams, [])
end
end
end
end
@@ -0,0 +1,45 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'initialize_materials'
require_relative '../states/state'
require_relative '../constants/observer_constants'
module SpeckleConnector
module Actions
# Switch sketchup model wit a new one
class LoadSketchupModel < Action
# Replace current model state with the state of a new model. This action is triggered when user opens new or
# existing Sketchup model.
# @param state [States::State] the current state of Speckle
# @param additional_parameters [Array] parameters that the action takes
# @return [States::State] the new updated state object
def self.update_state(state, sketchup_model)
# new_model_state = SketchupModel::Readers::ModelReader.read_model(sketchup_model)
# new_model_state = InitializeMaterials.update_state(new_model_state)
new_sketchup_state = state.sketchup_state.with(:@sketchup_model => sketchup_model)
new_state = state.with(:@sketchup_state => new_sketchup_state)
new_state = InitializeMaterials.update_state(new_state)
attach_observers(sketchup_model, new_state.speckle_state.observers)
new_state
end
# Attach observers to the sketchup model
# @param sketchup_model [Sketchup::Model] the model to attach observers to
# @param observers [Hash{Class=>}] the observer objects indexed by their class that will be attached
def self.attach_observers(sketchup_model, observers)
# selection = sketchup_model.selection
# selection.add_observer(observers[SELECTION_OBSERVER_NAME])
# layers = sketchup_model.layers
# layers.add_observer(observers[LAYERS_OBSERVER_NAME])
entities = sketchup_model.entities
entities.add_observer(observers[ENTITIES_OBSERVER])
sketchup_model.add_observer(observers[MODEL_OBSERVER])
# materials = sketchup_model.materials
# materials.add_observer(observers[MATERIALS_OBSERVER_NAME])
# pages = sketchup_model.pages
# pages.add_observer(observers[PAGES_OBSERVER_NAME])
end
end
end
end
@@ -0,0 +1,36 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../ext/sqlite3'
require_relative '../accounts/accounts'
require_relative '../constants/path_constants'
require_relative '../sketchup_model/dictionary/speckle_model_dictionary_handler'
module SpeckleConnector
module Actions
# When preference updated by UI.
class ModelPreferencesUpdated < Action
def initialize(pref, value)
super()
@preference = pref
@value = value
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
model = state.user_state.preferences[:model].dup
model[@preference.to_sym] = @value
new_preferences = state.user_state.preferences.put(:model, model)
SketchupModel::Dictionary::SpeckleModelDictionaryHandler.set_attribute(
state.sketchup_state.sketchup_model,
@preference.to_sym,
@value,
'Speckle'
)
new_user_state = state.user_state.with_preferences(new_preferences)
state.with_user_state(new_user_state)
end
end
end
end
@@ -0,0 +1,34 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'events/app_event_action'
require_relative 'events/entities_event_action'
require_relative 'events/model_event_action'
require_relative '../constants/observer_constants'
module SpeckleConnector
module Actions
# Handle events that were collected by observers
class OnEventsAction < Action
RUN_ORDER = {
APP_OBSERVER => Events::AppEventAction,
ENTITIES_OBSERVER => Events::EntitiesEventAction,
MODEL_OBSERVER => Events::ModelEventAction
# MATERIALS_OBSERVER => Events::MaterialsEventAction,
# LAYERS_OBSERVER => Events::LayerEventAction,
# PAGES_OBSERVER => Events::PagesEventAction,
# SELECTION_OBSERVER => Events::SelectionEventAction
}.freeze
def self.update_state(state, events)
RUN_ORDER.each do |observer_name, action|
next unless events.key?(observer_name)
parameters = events[observer_name]
state = action.update_state(state, parameters)
end
state
end
end
end
end
@@ -0,0 +1,45 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../accounts/accounts'
require_relative '../actions/create_stream'
require_relative '../actions/queue_send'
require_relative '../convertors/to_speckle'
module SpeckleConnector
module Actions
# Sends to speckle.
class OneClickSend < 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)
puts 'send to speckle'
default_account = Accounts.default_account
if default_account.nil?
puts 'No local account found. Please refer to speckle.guide for more information.'
return state
end
sketchup_model = state.sketchup_state.sketchup_model
to_convert = sketchup_model.selection.count > 0 ? sketchup_model.selection : sketchup_model.entities
first_saved_stream = first_saved_stream(sketchup_model)
action = if first_saved_stream.nil?
Actions::CreateStream.new
else
Actions::QueueSend.new(first_saved_stream, convert_to_speckle(sketchup_model, to_convert))
end
action.update_state(state)
end
def self.first_saved_stream(model)
(saved_streams = model.attribute_dictionary('speckle', true)['streams']) or []
saved_streams.nil? || saved_streams.empty? ? nil : saved_streams[0]
end
def self.convert_to_speckle(sketchup_model, to_convert)
converter = Converters::ToSpeckle.new(sketchup_model)
to_convert.map { |entity| converter.convert(entity) }
end
end
end
end
@@ -0,0 +1,32 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../states/state'
require_relative '../states/speckle_state'
require_relative '../actions/send_from_queue'
module SpeckleConnector
module Actions
# Send queue from state.
class QueueSend < Action
def initialize(stream_id, converted)
super()
@stream_id = stream_id
@converted = converted
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
to_send = { stream_id: @stream_id, converted: @converted }
new_speckle_state = state.speckle_state.with(:@stream_queue => to_send)
new_state = state.with(:@speckle_state => new_speckle_state)
if new_state.is_connected
action = Actions::SendFromQueue.new(@stream_id)
new_state = action.update_state(new_state)
end
new_state
end
end
end
end
@@ -0,0 +1,33 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../convertors/units'
require_relative '../convertors/to_native'
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)
super()
@stream_id = stream_id
@base = base
@stream_name = stream_name
@branch_name = branch_name
@branch_id = branch_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)
converter = Converters::ToNative.new(state)
# Have side effects on the sketchup model. It effects directly on the entities by adding new objects.
start_time = Time.now.to_f
converter.receive_commit_object(@base, state.user_state.preferences[:model])
elapsed_time = (Time.now.to_f - start_time).round(3)
puts "==== Converting to Native executed in #{elapsed_time} sec ===="
state.with_add_queue('finishedReceiveInSketchup', @stream_id, [])
end
end
end
end
@@ -0,0 +1,22 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../accounts/accounts'
require_relative 'load_saved_streams'
module SpeckleConnector
module Actions
# Action to reload accounts from database.
class ReloadAccounts < 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 'Reload of Speckle accounts requested by plugin'
new_speckle_state = state.speckle_state.with_accounts(Accounts.load_accounts)
state = state.with_speckle_state(new_speckle_state)
accounts_data = state.speckle_state.accounts
state.with_add_queue('loadAccounts', accounts_data.to_json, [])
end
end
end
end
@@ -0,0 +1,29 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../accounts/accounts'
require_relative '../convertors/units'
require_relative '../convertors/converter'
module SpeckleConnector
module Actions
# Action to remove stream.
# Currently it is not a state changer.
class RemoveStream < 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)
speckle_dict = state.sketchup_state.sketchup_model.attribute_dictionary('Speckle', true)
saved = speckle_dict['saved_streams'] || []
saved -= [@stream_id]
speckle_dict['saved_streams'] = saved
state
end
end
end
end
@@ -0,0 +1,27 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../accounts/accounts'
module SpeckleConnector
module Actions
# Save stream.
# Currently it is not a state changer.
class SaveStream < 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)
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['saved_streams'] = saved
state
end
end
end
end
@@ -0,0 +1,27 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../accounts/accounts'
module SpeckleConnector
module Actions
# Send already converted objects from queue if exist on stream.
class SendFromQueue < 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)
to_send_stream_id = state.speckle_state.stream_queue[: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.with_empty_stream_queue
end
end
end
end
@@ -0,0 +1,36 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'deactivate_diffing'
require_relative '../convertors/units'
require_relative '../convertors/to_speckle'
module SpeckleConnector
module Actions
# Send selection to server.
class SendSelection < 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, {})
converter = Converters::ToSpeckle.new(state)
new_speckle_state, base = converter.convert_selection_to_base(state.user_state.preferences)
id, total_children_count, batches, new_speckle_state = converter.serialize(base, new_speckle_state,
state.user_state.preferences,
@stream_id)
puts("converted #{base.count} objects for stream #{@stream_id}")
new_state = state.with_speckle_state(new_speckle_state.with_invalid_streams_queue)
new_state.with_add_queue('convertedFromSketchup', @stream_id, [
{ is_string: false, val: batches },
{ is_string: true, val: id },
{ is_string: false, val: total_children_count }
])
end
end
end
end
@@ -0,0 +1,53 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../ext/sqlite3'
require_relative '../accounts/accounts'
require_relative '../constants/path_constants'
module SpeckleConnector
module Actions
# When preference updated by UI.
class UserPreferencesUpdated < Action
def initialize(pref_hash, pref, value)
super()
@preference_hash = pref_hash
@preference = pref
@value = value
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
# Init sqlite database
db = Sqlite3::Database.new(SPECKLE_CONFIG_DB_PATH)
# Select data
data = db.exec("SELECT content FROM 'objects' WHERE hash = '#{@preference_hash}'").first.first
# Parse string to hash
data_hash = JSON.parse(data).to_h
# Get current preference value
old_preference_value = data_hash[@preference]
# Return old state if it is equal to new one
return state if @value == old_preference_value
data_hash[@preference] = @value
# Update entry unless equal old to new
db.exec("UPDATE 'objects' SET content = '#{data_hash.to_json}' WHERE hash = '#{@preference_hash}'")
# Close db when process done
db.close
user = state.user_state.preferences[:user].dup
user[@preference.to_sym] = @value
new_preferences = state.user_state.preferences.put(:user, user)
new_user_state = state.user_state.with_preferences(new_preferences)
state.with_user_state(new_user_state)
end
end
end
end
@@ -0,0 +1,53 @@
# frozen_string_literal: true
require_relative '../actions/clear_queue'
module SpeckleConnector
module App
# Application for the Speckle Connector.
class SpeckleConnectorApp
# @return [Commands::MenuCommandHandler] the commands registered in the extension menu in Sketchup
attr_reader :menu_commands
# @return [States::State] the current states of the app
attr_reader :state
# @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
@ui_controller = ui_controller
end
def speckle_loaded?
state.speckle_state?
end
def update_ui!
ui_controller.update_ui(state)
end
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
def add_observer_handler!(observer_handler)
@observer_handler = observer_handler
end
def update_state!(action, *parameters)
old_state = @state
@state = action.update_state(old_state, *parameters)
send_messages! if @state.speckle_state.message_queue.any?
update_ui! unless @state.equal?(old_state)
end
end
end
end
@@ -0,0 +1,39 @@
# frozen_string_literal: true
module SpeckleConnector
module Callbacks
# Helper class to serialize messages to send dialog.
class CallbackMessage
# @param callback_name [String] name of the callback command
# @param stream_id [String] id of the stream
# @param parameters [Array<String>] parameters of the callback method call
def self.serialize(callback_name, stream_id, parameters)
if parameters.any?
serialize_with_parameters(callback_name, stream_id, parameters)
else
serialize_without_parameters(callback_name, stream_id)
end
end
# @param callback_name [String] name of the callback command
# @param stream_id [String] id of the stream
# @param parameters [Array<Object>] parameters of the callback method call
def self.serialize_with_parameters(callback_name, stream_id, parameters)
message = "#{callback_name}('#{stream_id}'"
parameters.each { |par| message += par[:is_string] ? ",'#{par[:val]}'" : ",#{par[:val]}" }
message += ')'
message
end
# @param callback_name [String] name of the callback command
# @param stream_id [String] id of the stream
def self.serialize_without_parameters(callback_name, stream_id)
if %w[setSavedStreams loadAccounts].include?(callback_name)
"#{callback_name}(#{stream_id})"
else
"#{callback_name}('#{stream_id}')"
end
end
end
end
end
@@ -0,0 +1,24 @@
# frozen_string_literal: true
require_relative 'command'
module SpeckleConnector
module Commands
# Command to update state of the application.
class ActionCommand < Command
# @param app [App::SpeckleConnectorApp] the app object to run command on
# @param action [#update_state] the action that knows how to change the state of the speckle app
def initialize(app, action)
super(app)
@app = app
@action = action
end
private
def _run(*parameters)
app.update_state!(@action, *parameters)
end
end
end
end
@@ -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
+42
View File
@@ -0,0 +1,42 @@
# frozen_string_literal: true
module SpeckleConnector
module Commands
# Base command schema to wrap common operations for all commands.
class Command
# @return [App::SpeckleConnectorApp] the main app object
attr_reader :app
# @return [Ui::View] view object holds dialog and it's state
attr_reader :view
# @@param app [App::SpeckleConnectorApp] the main app object
def initialize(app)
@app = app
@view = app.ui_controller.user_interfaces[Ui::SPECKLE_UI_ID]
end
def run(*parameters)
# Run here common operations that same for each command.
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
end
end
end
@@ -0,0 +1,15 @@
# frozen_string_literal: true
require_relative 'command'
module SpeckleConnector
module Commands
# Run this command when the UI is ready to get data
class DialogReady < Command
# Update the selected user interface
def _run(_data)
view.update_view(app.state)
end
end
end
end
@@ -0,0 +1,51 @@
# frozen_string_literal: true
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
# Command to initialize Speckle UI and register it to ui_controller.
# This is the command where we show UI to user.
class InitializeSpeckle < Command
def dialog_title
"Speckle #{CONNECTOR_VERSION}"
end
private
def _run
app = self.app
unless app.state.instance_of?(States::InitialState)
vue_view = app.ui_controller.user_interfaces[Ui::SPECKLE_UI_ID]
vue_view.show
return
end
initialize_speckle(app)
end
# Do the actual Speckle initialization.
def initialize_speckle(app)
# TODO: Initialize here speckle states and observers.
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,
dialog_title: dialog_title,
height: 950,
width: 300
}
vue_view = Ui::VueView.new(dialog_specs, app)
app.ui_controller.register_ui(Ui::SPECKLE_UI_ID, vue_view)
vue_view.show
end
end
end
end
@@ -0,0 +1,51 @@
# frozen_string_literal: true
module SpeckleConnector
module Commands
# Helper class to register, handle menu and toolbar commands.
class MenuCommandHandler
# @param command [#run] command that can be run
# @param menu_text [String] name of the command that will be displayed on the menu
# @return [UI::Command] the command that can be added to Sketchup menu or toolbar
def self.sketchup_command(command, menu_text)
UI::Command.new(menu_text) do
command.run
end
end
# Validate if the user has started the Speckle and return a status code that can be used by
# {UI::Command#set_validation_proc} to disable menu entries and toolbar entries before Speckle is loaded.
def self.speckle_started(app)
return MF_ENABLED if app.speckle_loaded?
MF_GRAYED
end
def initialize
@menu_commands = {}
@added_to_menu = []
@added_to_toolbar = []
end
def []=(command_id, command)
@menu_commands[command_id] = command
end
# Add command to menu.
def add_to_menu!(command_id, menu)
return if @added_to_menu.include? command_id
menu.add_item(@menu_commands[command_id])
@added_to_menu << command_id
end
# Add command to toolbar.
def add_to_toolbar!(command_id, toolbar)
return if @added_to_toolbar.include? command_id
toolbar.add_item(@menu_commands[command_id])
@added_to_toolbar << command_id
end
end
end
end
@@ -0,0 +1,18 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../accounts/accounts'
require_relative '../actions/model_preference_updated'
module SpeckleConnector
module Commands
# Command to update theme.
class ModelPreferencesUpdated < Command
def _run(data)
preference = data['preference']
new_value = data['value']
app.update_state!(Actions::ModelPreferencesUpdated.new(preference, new_value))
end
end
end
end
@@ -0,0 +1,18 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/connected'
require_relative '../actions/send_from_queue'
module SpeckleConnector
module Commands
# Command to notify connected.
class NotifyConnected < Command
def _run(data)
stream_id = data['stream_id']
app.update_state!(Actions::Connected)
app.update_state!(Actions::SendFromQueue.new(stream_id))
end
end
end
end
@@ -0,0 +1,21 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/receive_objects'
module SpeckleConnector
module Commands
# Command to receive objects from Speckle Server.
class ReceiveObjects < Command
def _run(data)
stream_id = data['stream_id']
base = data['base']
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)
app.update_state!(action)
end
end
end
end
@@ -0,0 +1,19 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/remove_stream'
require_relative '../actions/load_saved_streams'
module SpeckleConnector
module Commands
# Command to remove stream.
class RemoveStream < Command
def _run(data)
stream_id = data['stream_id']
action = Actions::RemoveStream.new(stream_id)
app.update_state!(action)
app.update_state!(Actions::LoadSavedStreams)
end
end
end
end
@@ -0,0 +1,18 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../accounts/accounts'
require_relative '../actions/save_stream'
require_relative '../actions/load_saved_streams'
module SpeckleConnector
module Commands
# Command to saved stream.
class SaveStream < Command
def _run(data)
stream_id = data['stream_id']
app.update_state!(Actions::SaveStream.new(stream_id))
end
end
end
end
@@ -0,0 +1,17 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/send_selection'
module SpeckleConnector
module Commands
# Command to send selection to Speckle Server.
class SendSelection < Command
def _run(data)
stream_id = data['stream_id']
action = Actions::SendSelection.new(stream_id)
app.update_state!(action)
end
end
end
end
@@ -0,0 +1,58 @@
# frozen_string_literal: true
require_relative 'menu_command_handler'
require_relative 'action_command'
require_relative 'initialize_speckle'
require_relative '../actions/one_click_send'
module SpeckleConnector
module Commands
# Speckle menu commands that adds them to Sketchup menu and toolbar.
class SpeckleMenuCommands
CMD_INITIALIZE_SPECKLE = :initialize_speckle
CMD_SEND_TO_SPECKLE = :send_to_speckle
CMD_RECEIVE_FROM_SPECKLE = :receive_from_speckle
# Add initial set of commands to Speckle application object and to Sketchup menu and toolbar
# @param app [App::SpeckleConnectorApp] the application object
def self.add_initial_commands!(app)
commands = app.menu_commands
ui_controller = app.ui_controller
sketchup_ui = ui_controller.sketchup_ui
speckle_menu = sketchup_ui.speckle_menu
speckle_toolbar = sketchup_ui.speckle_toolbar
commands[CMD_INITIALIZE_SPECKLE] = initialize_speckle_command(app)
commands.add_to_menu!(CMD_INITIALIZE_SPECKLE, speckle_menu)
commands.add_to_toolbar!(CMD_INITIALIZE_SPECKLE, speckle_toolbar)
# commands[CMD_SEND_TO_SPECKLE] = send_command(app)
# commands.add_to_menu!(CMD_SEND_TO_SPECKLE, speckle_menu)
# commands.add_to_toolbar!(CMD_SEND_TO_SPECKLE, speckle_toolbar)
end
def self.initialize_speckle_command(app)
cmd = MenuCommandHandler.sketchup_command(
InitializeSpeckle.new(app), 'Initialize Speckle'
)
cmd.tooltip = 'Launch Connector'
cmd.status_bar_text = 'Opens the Speckle Connector window'
cmd.small_icon = '../../img/s2logo.png'
cmd.large_icon = '../../img/s2logo.png'
cmd
end
def self.send_command(app)
cmd = MenuCommandHandler.sketchup_command(
ActionCommand.new(app, Actions::OneClickSend), 'Send to Speckle'
)
cmd.tooltip = 'Send to Speckle'
cmd.status_bar_text = 'Send to Speckle'
cmd.small_icon = '../../img/Sender.png'
cmd.large_icon = '../../img/Sender.png'
cmd.set_validation_proc { MenuCommandHandler.speckle_started(app) }
cmd
end
end
end
end
@@ -0,0 +1,19 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../accounts/accounts'
require_relative '../actions/user_preferences_updated'
module SpeckleConnector
module Commands
# Command to update preferences.
class UserPreferencesUpdated < Command
def _run(data)
preference_hash = data['preference_hash']
preference = data['preference']
new_value = data['value']
app.update_state!(Actions::UserPreferencesUpdated.new(preference_hash, preference, new_value))
end
end
end
end
@@ -0,0 +1,12 @@
# frozen_string_literal: true
module SpeckleConnector
SPECKLE_BASE_OBJECT = 'Speckle_Base_Object'
SPECKLE_ID = 'speckle_id'
SPECKLE_TYPE = 'speckle_type'
APPLICATION_ID = 'application_id'
TOTAL_CHILDREN_COUNT = 'total_children_count'
PARENT = 'parent'
VALID_STREAM_IDS = 'valid_stream_ids'
INVALID_STREAM_IDS = 'invalid_stream_ids'
end
@@ -0,0 +1,22 @@
# frozen_string_literal: true
module SpeckleConnector
MAT_DICTIONARY = 'Speckle_Connector_Materials'
MAT_ID = 'Speckle_Connector_Material_Id'
MAT_ADD = :speckle_connector_add_material
MAT_EDIT = :speckle_connector_edit_material
MAT_REMOVE = :speckle_connector_remove_material
DEFAULT_COLORS = {
MAT_ADD => '#66FF66',
MAT_EDIT => '#FFFF9F',
MAT_REMOVE => '#FF6666'
}.freeze
DEFAULT_NAMES = {
MAT_ADD => 'Speckle_Material_Add',
MAT_EDIT => 'Speckle_Material_Edit',
MAT_REMOVE => 'Speckle_Material_Remove'
}.freeze
end
@@ -0,0 +1,7 @@
# frozen_string_literal: true
module SpeckleConnector
APP_OBSERVER = 'SpeckleConnector::Observers::AppObserver'
ENTITIES_OBSERVER = 'SpeckleConnector::Observers::EntitiesObserver'
MODEL_OBSERVER = 'SpeckleConnector::Observers::ModelObserver'
end
@@ -0,0 +1,24 @@
# frozen_string_literal: true
require 'pathname'
require_relative 'platform_constants'
# Speckle connector module to enable multiplayer mode ON!
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
path = ENV.fetch('APPDATA')
Pathname.new(File.join(path, 'Speckle')).cleanpath.to_s
when OS_MAC
File.join(Dir.home, 'Library/Application Support/Speckle')
else
raise 'Speckle could not determine your Appdata path'
end
SPECKLE_ACCOUNTS_DB_PATH = File.join(SPECKLE_APPDATA_PATH, 'Accounts.db')
SPECKLE_CONFIG_DB_PATH = File.join(SPECKLE_APPDATA_PATH, 'Config.db')
SPECKLE_TEST_DB_PATH = File.join(SPECKLE_APPDATA_PATH, 'sketchup_test.db')
end
@@ -0,0 +1,18 @@
# frozen_string_literal: true
# rubocop:disable Style/Documentation
module SpeckleConnector
host_os = RbConfig::CONFIG['host_os']
OS_WIN = :windows
OS_MAC = :macos
OPERATING_SYSTEM = case host_os
when /mswin|msys|mingw|cygwin|bccwin|wince|emc/
OS_WIN
when /darwin|mac os/
OS_MAC
else
raise "Unsupported OS: #{host_os.inspect}"
end
RUBY_VERSION_NUMBER = RUBY_VERSION.split('.')[0..1].join.to_i
end
# rubocop:enable Style/Documentation
@@ -0,0 +1,5 @@
# frozen_string_literal: true
module SpeckleConnector
BASE_OBJECT = 'Base'
end
@@ -0,0 +1,350 @@
# frozen_string_literal: true
# rubocop:disable SketchupPerformance/OpenSSL
require 'securerandom'
# rubocop:enable SketchupPerformance/OpenSSL
require 'digest'
require_relative 'converter'
require_relative '../relations/many_to_one_relation'
module SpeckleConnector
module Converters
# Serializer of the base object.
# Responsible to create id (hash) of the objects by holding their lineage and detaching relationships.
class BaseObjectSerializer
# @return [Integer] default chunk size the determine splitting base prop into chucks
attr_reader :default_chunk_size
# @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 = []
@family_tree = {}
@family_tree_relation = Relations::ManyToOneRelation.new
@closure_table = {}
@objects = {}
end
# @param base [Object] top base object to populate all children and their relationship
# @return [String, String] id (hash) and traversed hash
def serialize(base)
id, traversed = traverse_base(base)
@objects[id] = traversed
id
end
def total_children_count(id)
@objects[id][:totalChildrenCount]
end
# @param base_and_entities [Object] base object to populate all children and their relationship
# rubocop:disable Metrics/MethodLength
# 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. 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)
# 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`
# 5. Add closures
closure = {}
parent = @lineage.pop
unless @family_tree[parent].nil?
@family_tree[parent].each do |ref, depth|
closure[ref] = depth - @detach_lineage.length
end
end
# 6. Add total children count
traversed_base[:totalChildrenCount] = closure.keys.length
# 7. Finally create id
id = get_id(traversed_base)
# 8. Add id to traversed base
traversed_base[:id] = id
# 9. Update __closure table on the traversed base
unless traversed_base[:totalChildrenCount].nil?
@closure_table[id] = closure
traversed_base[:__closure] = closure unless closure.empty?
end
# 10. Save object string if detached
@objects[id] = traversed_base if is_detached
if @preferences[:user][:diffing] && !entities.nil?
entities.uniq.each do |entity|
speckle_entity = if speckle_state.speckle_entities.keys.include?(entity.persistent_id)
speckle_state.speckle_entities[entity.persistent_id].with_valid_stream_id(stream_id)
else
SpeckleEntities.with_converted(entity, traversed_base, stream_id)
end
@speckle_state = speckle_state.with_speckle_entity(speckle_entity)
end
end
return id, traversed_base
end
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/BlockLength
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def traverse_base_props(base, traversed_base)
base.each do |prop, value|
# 3.1. Ignore nil, starts with '_' and 'id'
next if value.nil? || prop[0] == '_' || prop == 'id' || prop == :id
# 3.2. Pass primitives without any operation (string, numeric, boolean)
unless value.is_a?(Hash) || value.is_a?(Array)
traversed_base[prop] = value
next
end
# 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) && !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
# 3.5.2. Init empty array for chunks
chunks = []
# 3.5.3. Init empty data chunk core object
chunk = {
speckle_type: 'Speckle.Core.Models.DataChunk',
data: []
}
# 3.5.4. Iterate each element on array to fill them into chunks
value.each_with_index do |el, index|
# 3.5.4.1. If current index is the multiplier of the chunk size, then need to append chunk into chunks
# and reinitialize empty chunk for next batch
if (index % chunk_size == 0) && index != 0
chunks.append(chunk)
chunk = {
speckle_type: 'Speckle.Core.Models.DataChunk',
data: []
}
end
# 3.5.4.2. Add element into chunk
chunk[:data].append(el)
end
# 3.5.5. Add trailing batch to the chunks also unless is empty
chunks.append(chunk) unless chunk[:data].empty?
# 3.5.6. Initialize empty chunk reference array
chunk_references = []
chunks.each do |chunk_element|
@detach_lineage.append(is_detach_prop)
id, _traversed = traverse_base(chunk_element)
chunk_references.append(detach_helper(id))
end
# 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 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
traversed_base[prop] = if is_base
is_detach_prop ? detach_helper(child[:id]) : child
else
child
end
end
end
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/BlockLength
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# Whether value has a pattern [<converted>, [<entity>, <entity>, ... <entity>]] or not.
def base_and_entities?(value)
is_array = value.is_a?(Array)
return false unless is_array
return false unless is_array && value.length == 2
value[1].all? { |v| v.is_a?(Sketchup::Entity) }
end
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Style/OptionalBooleanParameter
# rubocop:disable Metrics/AbcSize
def traverse_value(value, is_detach = false)
# 1. Return same value if value is primitive type (string, numeric, boolean)
return value unless value.is_a?(Hash) || value.is_a?(Array)
# 2. 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
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?(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
el_value = traverse_value(el, is_detach)
detached_list.append(el_value)
end
end
return detached_list
end
# 3. Hash
return value if value.is_a?(Hash) && value[:speckle_type].nil?
# 4. Base objects
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
end
# 5. If it is not returned until here then there is unsupported type
raise StandardError "Unsupported type #{value.class} : #{value}"
end
# rubocop:enable Metrics/MethodLength
# 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|
# init parent on the family tree unless exist
@family_tree[parent] = {} if @family_tree[parent].nil?
is_ref_exist = !@family_tree[parent].nil? && !@family_tree[parent][reference_id].nil?
if !is_ref_exist || @family_tree[parent][reference_id] > @detach_lineage.length
@family_tree[parent][reference_id] = @detach_lineage.length
end
end
{
referencedId: reference_id,
speckle_type: 'reference'
}
end
# @param traversed_base [SpeckleConnector::SpeckleObjects::Base] traversed base object.
def get_id(traversed_base)
Digest::MD5.hexdigest(traversed_base.to_json)
end
# rubocop:disable Metrics/MethodLength
def batch_objects(max_batch_size_mb = 1)
max_size = 1000 * 1000 * max_batch_size_mb
batches = []
batch = '['
batch_size = 0
objects = @objects.values
objects.each do |obj|
obj_json = obj.to_json
if batch_size + obj_json.length < max_size
batch += obj_json
batch += ','
batch_size += obj_json.length
else
batch = batch.chop
batches.append("#{batch}]")
batch = "[#{obj_json},"
batch_size = obj_json.length
end
end
batch = batch.chop
batches.append("#{batch}]")
batches
end
# rubocop:enable Metrics/MethodLength
# @param entity [Sketchup::Entity] source entity object
# @param speckle_state [States::SpeckleState] the current speckle state of the {States::State}
def check_base_available_on_state(entity, speckle_state)
is_exist = speckle_state.speckle_entities.keys.include?(entity.persistent_id)
return is_exist unless is_exist
speckle_state.speckle_entities[entity.persistent_id].valid_stream_ids.include?(stream_id)
end
end
end
end
@@ -0,0 +1,138 @@
# frozen_string_literal: true
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
module SpeckleConnector
module Converters
# CleanUp is a plugin developed by [Thomas Thomassen](https://github.com/thomthom).
module CleanUp
# Removes coplanar entities from the given entities.
# @param entities [Sketchup::Entities] entities to remove edges between that make entities coplanar.
# @note Merging coplanar faces idea originated from [CleanUp](https://github.com/thomthom/cleanup) plugin
# which is developed by [Thomas Thomassen](https://github.com/thomthom).
def self.merge_coplanar_faces(entities)
edges = []
faces = entities.collect { |entity| entity if entity.is_a? Sketchup::Face }.compact
faces.each { |face| face.edges.each { |edge| edges << edge } }
edges.uniq!
edges.each { |edge| remove_edge_have_coplanar_faces(edge, faces, false) }
end
# Detect edges to remove by checking following controls respectively;
# - Upcoming Sketchup entity is Sketchup::Edge or not.
# - Whether edge has 2 face or not.
# - Whether faces are duplicated or not.
# - Whether edges safe to merge or not.
# - Whether faces have same material or not.
# - Whether UV texture map is aligned between faces or not.
# - Finally, if faces are coplanar by correcting these checks, then removes edge from Sketchup.active_model.
# @param edge [Sketchup::Edge] edge to check.
# @param faces [Array<Sketchup::Face>] scoped faces to check 'edge.faces' both (first and second)
# belongs to this faces or not. If any of this faces does not involve this scoped faces, then do not delete.
# @param ignore_materials [Boolean] whether ignore materials or not.
# Returns true if the given edge separating two coplanar faces.
# Return false otherwise.
# rubocop:disable Metrics/AbcSize
def self.remove_edge_have_coplanar_faces(edge, faces, ignore_materials)
return false unless edge.valid? && edge.is_a?(Sketchup::Edge)
return false unless edge.faces.size == 2
# Check scoped faces have this edges
if edge.faces.size == 2
is_first = faces.include?(edge.faces[0])
is_second = faces.include?(edge.faces[1])
return false unless is_first && is_second
end
face_1, face_2 = edge.faces
return false if face_duplicate?(face_1, face_2)
# Check for troublesome faces which might lead to missing geometry if merged.
return false unless edge_safe_to_merge?(edge)
# Check materials match.
unless ignore_materials
return false unless (face_1.material == face_2.material) && (face_1.back_material == face_2.back_material)
# Verify UV mapping match.
return false if !face_1.material.nil? && !continuous_uv?(face_1, face_2, edge) && face_1.material.texture.nil?
end
# Check faces are coplanar or not.
return false unless faces_coplanar?(face_1, face_2)
edge.erase!
true
end
# rubocop:enable Metrics/AbcSize
# Determines if two faces are overlapped.
def self.face_duplicate?(face_1, face_2, overlapping: false)
return false if face_1 == face_2
v_1 = face_1.outer_loop.vertices
v_2 = face_2.outer_loop.vertices
return true if (v_1 - v_2).empty? && (v_2 - v_1).empty?
if overlapping && (v_2 - v_1).empty?
edges = (face_2.outer_loop.edges - face_1.outer_loop.edges)
unless edges.empty?
point = edges[0].start.position.offset(edges[0].line[1], 0.01)
return true if face_1.classify_point(point) <= 4
end
end
false
end
# Checks the given edge for potential problems if the connected faces would
# be merged.
def self.edge_safe_to_merge?(edge)
edge.faces.all? { |face| face_safe_to_merge?(face) }
end
# Returns true if the two faces connected by the edge has continuous UV mapping.
# UV's are normalized to 0.0..1.0 before comparison.
def self.continuous_uv?(face_1, face_2, edge)
tw = Sketchup.create_texture_writer
uvh_1 = face_1.get_UVHelper(true, true, tw)
uvh_2 = face_2.get_UVHelper(true, true, tw)
p_1 = edge.start.position
p_2 = edge.end.position
uv_equal?(uvh_1.get_front_UVQ(p_1), uvh_2.get_front_UVQ(p_1)) &&
uv_equal?(uvh_1.get_front_UVQ(p_2), uvh_2.get_front_UVQ(p_2)) &&
uv_equal?(uvh_1.get_back_UVQ(p_1), uvh_2.get_back_UVQ(p_1)) &&
uv_equal?(uvh_1.get_back_UVQ(p_2), uvh_2.get_back_UVQ(p_2))
end
# Normalize UV's to 0.0..1.0 and compare them.
def self.uv_equal?(uvq_1, uvq_2)
uv_1 = uvq_1.to_a.map { |n| n % 1 }
uv_2 = uvq_2.to_a.map { |n| n % 1 }
uv_1 == uv_2
end
# Validates that the given face can be merged with other faces without causing
# problems.
def self.face_safe_to_merge?(face)
stack = face.outer_loop.edges
edge = stack.shift
direction = edge.line[1]
until stack.empty?
edge = stack.shift
return true unless edge.line[1].parallel?(direction)
end
false
end
# Determines if two faces are coplanar.
def self.faces_coplanar?(face_1, face_2)
vertices = face_1.vertices + face_2.vertices
plane = Geom.fit_plane_to_points(vertices)
vertices.all? { |v| v.position.on_plane?(plane) }
end
end
end
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
@@ -0,0 +1,24 @@
# frozen_string_literal: true
module SpeckleConnector
module Converters
# Helper class to convert geometries between server and Sketchup.
class Converter
# @return [Sketchup::Model] active sketchup model.
attr_reader :sketchup_model
# @return [String] speckle units
attr_reader :units
attr_accessor :definitions
# @param sketchup_state [States::SketchupState] the current sketchup state of the {States::State}
def initialize(sketchup_state)
@sketchup_model = sketchup_state.sketchup_model
su_unit = sketchup_state.length_units
@units = Converters::SKETCHUP_UNITS[su_unit]
@definitions = {}
end
end
end
end
@@ -0,0 +1,295 @@
# frozen_string_literal: true
require_relative 'converter'
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/geometry/point'
require_relative '../speckle_objects/geometry/line'
require_relative '../speckle_objects/geometry/mesh'
module SpeckleConnector
module Converters
# Converts sketchup entities to speckle objects.
class ToNative < Converter
# Module aliases
GEOMETRY = SpeckleObjects::Geometry
OTHER = SpeckleObjects::Other
# Class aliases
POINT = GEOMETRY::Point
LINE = GEOMETRY::Line
MESH = GEOMETRY::Mesh
BLOCK_DEFINITION = OTHER::BlockDefinition
BLOCK_INSTANCE = OTHER::BlockInstance
BASE_OBJECT_PROPS = %w[applicationId id speckle_type totalChildrenCount].freeze
CONVERTABLE_SPECKLE_TYPES = %w[
Objects.Geometry.Line
Objects.Geometry.Polyline
Objects.Geometry.Mesh
Objects.Geometry.Brep
Objects.Other.BlockInstance
Objects.Other.BlockDefinition
Objects.Other.RenderMaterial
].freeze
def initialize(state)
super(state.sketchup_state)
end
def can_convert_to_native(obj)
return false unless obj.is_a?(Hash) && obj.key?('speckle_type')
CONVERTABLE_SPECKLE_TYPES.include?(obj['speckle_type'])
end
def ignored_speckle_type?(obj)
['Objects.BuiltElements.Revit.Parameter'].include?(obj['speckle_type'])
end
# @param obj [Object] speckle commit object.
def receive_commit_object(obj, model_preferences)
# First create layers on the sketchup before starting traversing
filtered_layer_containers = obj.keys.filter_map { |key| key if key.start_with?('@') && key != '@Named Views' }
create_layers(filtered_layer_containers, sketchup_model.layers)
create_views(obj.filter_map { |key, value| value if key == '@Named Views' }, sketchup_model)
# Define default commit layer which is the fallback
default_commit_layer = sketchup_model.layers.layers.find { |layer| layer.display_name == '@Untagged' }
traverse_commit_object(obj, sketchup_model.layers, default_commit_layer, model_preferences)
end
# Create actual Sketchup layers from layer_paths that taken from Speckle base object.
# @param layer_paths [Array<String>] layer paths to decompose it to folders and it's layers.
# @param folder [Sketchup::Layers, Sketchup::LayerFolder] folder to create folders and layers under it.
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 views [Array] views.
# @param sketchup_model [Sketchup::Model] active sketchup model.
# rubocop:disable Metrics/AbcSize
def create_views(views, sketchup_model)
return if views.empty?
views.first.each do |view|
origin = view['origin']
target = view['target']
origin = SpeckleObjects::Geometry::Point.to_native(origin['x'], origin['y'], origin['z'], origin['units'])
target = SpeckleObjects::Geometry::Point.to_native(target['x'], target['y'], target['z'], target['units'])
# Set camera position before creating scene on it.
my_camera = Sketchup::Camera.new(origin, target, [0, 0, 1], !view['isOrthogonal'], view['lens'])
sketchup_model.active_view.camera = my_camera
sketchup_model.pages.add(view['name'])
page = sketchup_model.pages[view['name']]
set_page_update_properties(page, view['update_properties'])
set_rendering_options(page.rendering_options, view['rendering_options'])
end
end
# rubocop:enable Metrics/AbcSize
# @param page [Sketchup::Page] scene to update -update properties-
def set_page_update_properties(page, update_properties)
update_properties.each do |prop, value|
page.instance_variable_set(:"@#{prop}", value)
end
end
# @param rendering_options [Sketchup::RenderingOptions] rendering options of scene (page)
def set_rendering_options(rendering_options, speckle_rendering_options)
speckle_rendering_options.each do |prop, value|
next if rendering_options[prop].nil?
rendering_options[prop] = if value.is_a?(Hash)
SpeckleObjects::Others::Color.to_native(value)
else
value
end
end
end
# @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)
elsif obj.is_a?(Hash) && obj.key?('speckle_type')
return if ignored_speckle_type?(obj)
if obj['displayValue'].nil?
# 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)
end
else
# puts(">>> Found #{obj['speckle_type']}: #{obj['id']} with displayValue.")
convert_to_native(obj, layer, model_preferences)
end
elsif obj.is_a?(Hash)
obj.each_value { |value| traverse_commit_object(value, commit_folder, layer, model_preferences) }
elsif obj.is_a?(Array)
obj.each { |value| traverse_commit_object(value, commit_folder, layer, model_preferences) }
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
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
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)
end
rescue StandardError => e
puts("Failed to convert #{obj['speckle_type']} (id: #{obj['id']})")
puts(e)
nil
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/MethodLength
# Creates a component definition and instance from a speckle object with a display value
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/MethodLength
def display_value_to_native_component(obj, layer, entities, model_preferences, &convert)
obj_id = obj['applicationId'].to_s.empty? ? obj['id'] : obj['applicationId']
block_definition = obj['@blockDefinition'] || obj['blockDefinition']
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
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
end
end
@@ -0,0 +1,252 @@
# frozen_string_literal: true
require_relative 'converter'
require_relative 'base_object_serializer'
require_relative '../relations/many_to_one_relation'
require_relative '../speckle_entities/speckle_entities'
require_relative '../speckle_objects/base'
require_relative '../speckle_objects/geometry/line'
require_relative '../speckle_objects/geometry/length'
require_relative '../speckle_objects/geometry/mesh'
require_relative '../speckle_objects/other/block_instance'
require_relative '../speckle_objects/other/block_definition'
require_relative '../speckle_objects/other/rendering_options'
require_relative '../speckle_objects/built_elements/view3d'
require_relative '../constants/path_constants'
module SpeckleConnector
module Converters
# Converts sketchup entities to speckle objects.
class ToSpeckle < Converter
# @return [Hash{Symbol=>Array}] layers to hold it's objects under the base object.
attr_reader :layers
# @return [States::State] the current speckle state of the {States::State}
attr_reader :state
# @return [States::SpeckleState] the current speckle state of the {States::State}
attr_reader :speckle_state
# @return [Relations::ManyToOneRelation] relations between objects.
attr_accessor :relation
def initialize(state)
super(state.sketchup_state)
@state = state
@speckle_state = @state.speckle_state
@layers = add_all_layers
@relation = Relations::ManyToOneRelation.new
end
# Convert selected objects by putting them into related array that grouped by layer.
# @return [Hash{Symbol=>Array}] layers -which only have objects- to hold it's objects under the base object.
def convert_selection_to_base(preferences)
state = speckle_state
sketchup_model.selection.each do |entity|
new_speckle_state, converted_object_with_entity = convert(entity, preferences, state)
state = new_speckle_state
layer_name = entity_layer_path(entity)
layers[layer_name].push(converted_object_with_entity)
end
# send only+ layers that have any object
base_object_properties = layers.reject { |_layer_name, objects| objects.empty? }
add_views(base_object_properties) if sketchup_model.pages.any?
return state, SpeckleObjects::Base.with_detached_layers(base_object_properties)
end
# Add views from pages.
# @param base_object_properties [Hash] dynamically attached base object properties.
def add_views(base_object_properties)
views = []
sketchup_model.pages.each do |page|
cam = page.camera
origin = get_camera_origin(cam)
target = get_camera_target(cam)
direction = get_camera_direction(cam)
update_properties = get_scene_update_properties(page)
rendering_options = SpeckleObjects::Others::RenderingOptions.to_speckle(page.rendering_options)
view = SpeckleObjects::BuiltElements::View3d.new(
page.name, origin, target, direction, SpeckleObjects::Geometry::Vector.new(0, 0, 1, @units),
cam.perspective?, cam.fov, @units, page.name, update_properties, rendering_options
)
views.append(view)
end
base_object_properties['@Named Views'] = views
end
# Get scene properties
# @param page [Sketchup::Page] page on sketchup.
def get_scene_update_properties(page)
{
use_axes: page.use_axes?,
use_camera: page.use_camera?,
use_hidden_geometry: page.use_hidden_geometry?,
use_hidden_layers: page.use_hidden_layers?,
use_hidden_objects: page.use_hidden_objects?,
use_rendering_options: page.use_rendering_options?,
use_section_planes: page.use_section_planes?,
use_shadow_info: page.use_shadow_info?,
use_style: page.use_style?
}
end
# Serialized and traversed information to send batches.
# @param base_and_entity [SpeckleObjects::Base] base object to serialize.
# @param stream_id [String] stream id to send conversion
# @return [String, Integer, Array<Object>] base id, total_children_count of base and batches
def serialize(base_and_entity, speckle_state, preferences, stream_id)
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, 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.
# @param speckle_state [States::SpeckleState] the current speckle state of the {States::State}
# @param parent [Symbol, String] parent of the Sketchup Entity to be converted.
# rubocop:disable Metrics/MethodLength
def convert(entity, preferences, speckle_state, parent = :base)
convert = method(:convert)
if entity.is_a?(Sketchup::Edge)
line = SpeckleObjects::Geometry::Line.from_edge(entity, @units, preferences[:model]).to_h
return speckle_state, [line, [entity]]
end
if entity.is_a?(Sketchup::Face)
mesh = SpeckleObjects::Geometry::Mesh.from_face(entity, @units, preferences[:model])
return speckle_state, [mesh, [entity]]
end
if entity.is_a?(Sketchup::Group)
new_speckle_state, block_instance = SpeckleObjects::Other::BlockInstance.from_group(
entity, @units, preferences, speckle_state, &convert
)
speckle_state = new_speckle_state
return speckle_state, [block_instance, [entity]]
end
if entity.is_a?(Sketchup::ComponentInstance)
new_speckle_state, block_instance = SpeckleObjects::Other::BlockInstance.from_component_instance(
entity, @units, preferences, speckle_state, &convert
)
speckle_state = new_speckle_state
return speckle_state, [block_instance, [entity]]
end
if entity.is_a?(Sketchup::ComponentDefinition)
new_speckle_state, block_definition = SpeckleObjects::Other::BlockDefinition.from_definition(
entity, @units, @definitions, preferences, speckle_state, parent, &convert
)
speckle_state = new_speckle_state
return speckle_state, [block_definition, [entity]]
end
return speckle_state, nil
end
# rubocop:enable Metrics/MethodLength
# Create layers -> {Hash{Symbol=>Array}} from sketchup model with empty array as hash entry values.
# This method add first headless layers (not belong to any folder),
# 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
end
private
def get_camera_direction(cam)
SpeckleObjects::Geometry::Vector.new(
SpeckleObjects::Geometry.length_to_speckle(cam.direction[0], @units),
SpeckleObjects::Geometry.length_to_speckle(cam.direction[1], @units),
SpeckleObjects::Geometry.length_to_speckle(cam.direction[2], @units),
@units
)
end
def get_camera_target(cam)
SpeckleObjects::Geometry::Point.new(
SpeckleObjects::Geometry.length_to_speckle(cam.target[0], @units),
SpeckleObjects::Geometry.length_to_speckle(cam.target[1], @units),
SpeckleObjects::Geometry.length_to_speckle(cam.target[2], @units),
@units
)
end
def get_camera_origin(camera)
SpeckleObjects::Geometry::Point.new(
SpeckleObjects::Geometry.length_to_speckle(camera.eye[0], @units),
SpeckleObjects::Geometry.length_to_speckle(camera.eye[1], @units),
SpeckleObjects::Geometry.length_to_speckle(camera.eye[2], @units),
@units
)
end
end
end
end

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