Compare commits

...

269 Commits

Author SHA1 Message Date
Oğuzhan Koral 92a941a944 Fix (Instancing): correct id of definition for speckle entity 2023-11-29 13:25:51 +03:00
oguzhankoral 0e1ddf2b11 Tweak definition speckle entity checks 2023-11-29 13:15:07 +03:00
Oğuzhan Koral b57fa010d1 Fix (Config): Reset configs if configSketchup somehow corrupted 2023-11-28 22:38:56 +03:00
oguzhankoral f816452b78 Reset configs if configSketchup somehow corrupted 2023-11-28 22:36:03 +03:00
Oğuzhan Koral 120083bb31 Feat (Performance): receive performance improvements 2023-11-28 13:54:36 +03:00
oguzhankoral a5bb5c4686 Remove logging 2023-11-28 13:31:19 +03:00
oguzhankoral e5e2729f0a Wrap receive into sketchup operation for performance improvement 2023-11-28 11:57:17 +03:00
oguzhankoral ba8b902f48 Merge coplanar faces at the end of the operation 2023-11-28 11:23:30 +03:00
oguzhankoral 2d67815ae6 Remove unused lines 2023-11-27 23:36:10 +03:00
oguzhankoral ec0c9066d2 Merge coplanar faces remove scoping and check normals first 2023-11-27 21:44:15 +03:00
Oğuzhan Koral 58ae858077 Update dev with changes in main 2023-11-13 19:45:20 +03:00
Oğuzhan Koral 613e7938b3 Fix (Scene): missing views from Revit 2023-11-13 14:35:09 +03:00
oguzhankoral e07ff1a445 Fix missing views from Revit
- Previously it was assuming views arrive in elements of collection
2023-11-13 14:15:32 +03:00
Oğuzhan Koral de7dd34ea2 Feat (FE2): Support fe2 terminology and urls 2023-11-13 12:03:19 +03:00
oguzhankoral 0552f695f9 Update FE2 terms for Mapper Tool 2023-11-10 16:49:51 +03:00
oguzhankoral b8d4f3d946 Parse FE2 urls to add projects via ADD BY ID OR URL 2023-11-10 15:52:06 +03:00
oguzhankoral fa112a70b1 Switch to FE2 as user preferences 2023-11-10 15:13:58 +03:00
oguzhankoral 97309ebb88 Merge remote-tracking branch 'origin/development' 2023-09-19 08:31:29 +03:00
Oğuzhan Koral 556ddc0b6f Feat (deploy): Mac support 🍎 2023-09-11 10:40:08 +03:00
Oğuzhan Koral a0dde690ea Fix intendation 2023-09-08 18:17:59 +03:00
Oğuzhan Koral a76dab5be6 Remove mac suffix from deploy manager 2023-09-08 18:16:15 +03:00
Oğuzhan Koral 2d10bc5bbf Deploy manager for mac 2023-09-08 18:13:15 +03:00
Oğuzhan Koral 4042632e0b Fix (Revit): Support curtain walls from revit 2023-09-08 13:37:07 +03:00
oguzhankoral 7ccf83e1a4 Consider speckle id for revit definition 2023-09-08 13:17:16 +03:00
oguzhankoral 019cd0756f Convert revit walls to native instead with display value 2023-09-08 13:16:50 +03:00
József L. Kiss 0e5f9f80be CI integration of mac build (#301)
* patcher

* patch-version

* fix 1

* fix 2

* patch_version

* revert

* mac installer

* slname

* checkout

* powershell

* SEMVER

* python

* installername

* git checkout

* cd speckle-sharp-ci-tools

* zip

* zip location

* white space

* remove checktou

---------

Co-authored-by: József L. Kiss <>
2023-09-07 17:05:02 +03:00
József L. Kiss fc6767860a Add release version of sqlit3 bundle 2023-08-17 13:05:13 +02:00
oguzhankoral 5b5b4be7b2 Mac AppData folder 2023-08-16 17:22:25 +03:00
oguzhankoral 45351d082e Add sqlite3_27.bundle for mac 2023-08-16 16:30:43 +03:00
Oğuzhan Koral 22ccd07491 Release 2.15 2023-07-25 14:48:22 +03:00
Oğuzhan Koral 2cf9ee647b Fix (Mapper): Add offset parameter to walls 2023-07-25 14:26:21 +03:00
oguzhankoral efb567824b Add offset parameter to walls 2023-07-12 13:01:16 +03:00
Oğuzhan Koral f0aac39486 Chore (Mixpanel): tracking for mapper actions 2023-07-06 10:03:08 +03:00
oguzhankoral f278055805 Correct mappings set and applied 2023-07-03 12:27:23 +03:00
oguzhankoral 6f2e36fd11 Add mixpanel tracking for mapper 2023-07-03 12:03:37 +03:00
Oğuzhan Koral 119d80ffc8 Fix (Mapper): Definition mapping 2023-06-29 22:42:36 +03:00
oguzhankoral 771c3df864 Fix definition mapping 2023-06-29 22:39:31 +03:00
Oğuzhan Koral 7d1963e458 Feat (Mapper): Revit and default wall support 2023-06-26 12:23:52 +03:00
oguzhankoral dde85972b3 Add default wall 2023-06-26 12:20:32 +03:00
oguzhankoral 5e061da910 Correct tooltips according to source state 2023-06-26 11:49:34 +03:00
oguzhankoral 46bea345de RevitWall from baseline 2023-06-26 11:33:09 +03:00
Oğuzhan Koral bc53462ad6 Chore (Mapper): Rename Floor with RevitFloor 2023-06-26 10:47:50 +03:00
oguzhankoral 884df40a1d Rename Floor with RevitFloor 2023-06-26 10:45:40 +03:00
Oğuzhan Koral b23168c067 Fix (Mapper): evaluate mapping source family data 2023-06-26 10:07:48 +03:00
oguzhankoral 5568212f15 Correct type names from source 2023-06-25 20:48:16 +03:00
oguzhankoral 79db79d799 Remove braces from mapper 2023-06-25 20:47:54 +03:00
oguzhankoral 18a4008efd Set selected level name string instead of object 2023-06-25 20:20:17 +03:00
oguzhankoral 83e4abd1ee Update family types when family updated 2023-06-23 18:37:47 +03:00
oguzhankoral 243bcfba72 Make dropdowns functional 2023-06-23 18:33:39 +03:00
Oğuzhan Koral af2c8c560f Feat (Mapper): mapper method by selection and floor creation 2023-06-22 17:27:49 +03:00
oguzhankoral 6a37f3871c Add new types as constants 2023-06-22 17:18:00 +03:00
oguzhankoral 759a388448 Register levels as speckle entity 2023-06-22 17:17:47 +03:00
oguzhankoral 360e89d7ce Separate default floor with native floor 2023-06-22 17:17:23 +03:00
oguzhankoral 64655a3284 Pass speckle state to speckle conversions to detect levels 2023-06-22 17:16:47 +03:00
oguzhankoral c58356bde8 Rename with_mapper_selection_queue with better one 2023-06-22 17:16:06 +03:00
oguzhankoral f74117632d Pass stream id to level object to create it as speckle entity 2023-06-22 17:15:23 +03:00
oguzhankoral 5f2b8b8e2b Parse family type level info from command to action 2023-06-22 17:15:00 +03:00
oguzhankoral f0ce7481fd Register clear mapper source action to commands 2023-06-22 17:14:38 +03:00
oguzhankoral 6e7a5c6140 Apply and clear mapping source by buttons instead of auto-apply 2023-06-22 17:13:52 +03:00
oguzhankoral 079c18ee19 Apply mappings also for family, type and level 2023-06-22 17:13:25 +03:00
oguzhankoral 26ef6a3815 Add clearing mapper source action 2023-06-22 17:12:18 +03:00
oguzhankoral cf6dcefe6c Separate mapper data collection from Sketchup selection 2023-06-22 17:11:41 +03:00
oguzhankoral 35590eb979 Add revit parameter object 2023-06-22 17:11:07 +03:00
oguzhankoral 583a8b8a76 Separate mapper related read functions 2023-06-20 08:18:08 +03:00
Oğuzhan Koral c6e8a664de Feat (Mapper): Fetch mapper source 2023-06-19 15:10:43 +03:00
oguzhankoral 24a5e0a579 Update levels geometrically when we have new commit 2023-06-19 12:57:48 +03:00
oguzhankoral a242c197fb Store mapper source in ruby state 2023-06-16 23:10:50 +03:00
oguzhankoral 08bdd23149 Add state for mapper 2023-06-16 21:57:13 +03:00
oguzhankoral c5b35b2d98 Notify user when selected source has new update 2023-06-16 16:23:27 +03:00
oguzhankoral 5407fecd1f Fetch source branch last commit and pass to ruby 2023-06-15 16:10:10 +03:00
oguzhankoral 12d2821d26 Add mapping source selection 2023-06-15 12:09:39 +03:00
Oğuzhan Koral 2e6d58e6a3 Feat (Mapper): mapping for default floor 2023-06-14 22:20:21 +03:00
oguzhankoral 0f0c0fd5ae Check material nil 2023-06-14 16:50:04 +03:00
oguzhankoral ef9ec1c223 Send floors with global coordinates as flat list 2023-06-14 01:11:37 +03:00
oguzhankoral f4387bae30 Send @SpeckleSchema as detached property 2023-06-13 11:50:35 +03:00
oguzhankoral 49b238a23a Extract mapped elements on selection 2023-06-13 11:49:24 +03:00
oguzhankoral 4b75c01b28 Convert sketchup faces to built elements' floor 2023-06-12 13:36:09 +03:00
Oğuzhan Koral e7f641046b Fix (Send): Do not send hidden geometries in components 2023-06-09 15:26:16 +03:00
oguzhankoral 9aaabe0fab Do not send hidden geometries in components 2023-06-09 15:24:40 +03:00
Oğuzhan Koral b6e4b711bf Feat (Edge): support rhino curve types 2023-06-09 11:19:57 +03:00
oguzhankoral 58fcfd210b Support arc and circle 2023-06-09 11:12:04 +03:00
oguzhankoral 5868b9c234 Support polycurves as displayValues 2023-06-09 09:55:52 +03:00
Oğuzhan Koral 0dc6d9cf9d Fix (DisplayValue): Use name of display value first 2023-06-08 23:42:05 +03:00
oguzhankoral 67f50cf2fd If displayValue has name use it 2023-06-08 23:39:54 +03:00
Oğuzhan Koral 8b26a4d49a Chore (DisplayValue): Better component names for displayValues 2023-06-08 23:15:07 +03:00
oguzhankoral a1d0bb0aa1 Instance name as speckle_type only 2023-06-08 23:08:25 +03:00
oguzhankoral bfe08560b1 Use speckle type instead def 2023-06-08 22:31:28 +03:00
Oğuzhan Koral ac3ac24272 Fix (Scene): Orthogonal view receiving as zoomed in 2023-06-08 14:39:07 +03:00
oguzhankoral 03e7191d0e Remove unnecessary lines on to_native 2023-06-08 14:36:39 +03:00
oguzhankoral b3a42f8723 Extract camera creation fuction to fix rubocop warnings 2023-06-08 14:34:18 +03:00
oguzhankoral 9c4b740300 Send 35mm focal length if camera is orthogonal 2023-06-08 14:27:05 +03:00
oguzhankoral 2a12bdadf2 Set camera height for isometric views 2023-06-08 14:14:34 +03:00
Oğuzhan Koral c90e8ad4d2 Feat (Face): extrudable brep faces 2023-06-08 12:23:21 +03:00
oguzhankoral 4a52c51c86 Rename returning single faces as ngon 2023-06-08 12:19:09 +03:00
oguzhankoral 0efc817ddc Remove remaining orphan edges after clean up 2023-06-07 13:06:16 +03:00
oguzhankoral fee54fc98c Do not smooth and soft single faces 2023-06-07 13:05:58 +03:00
oguzhankoral 210f751396 Return added entities too from conversion 2023-06-07 13:05:35 +03:00
Oğuzhan Koral 2e2bc3fe29 Feat (Layers): support flat layers 2023-06-06 13:35:05 +03:00
oguzhankoral a6f05f86d1 Document layer strategies 2023-06-06 13:33:37 +03:00
oguzhankoral fc144e4848 Convert layers as flat list from Rhino 2023-06-06 11:43:22 +03:00
oguzhankoral cef9531428 Log function to help write texts to local file 2023-06-06 11:42:54 +03:00
oguzhankoral abd4faefbf Move speckle entity creation from speckle object to SpeckleEntity class 2023-06-06 08:17:31 +03:00
Oğuzhan Koral 966f7aaed5 Fix (Collection): fix blender receive 2023-05-29 18:12:02 +03:00
oguzhankoral 51b59fa995 Fix typo on including string 2023-05-29 18:09:48 +03:00
oguzhankoral 0b713736bd Include also detached @elements props to displayValue 2023-05-29 18:06:05 +03:00
oguzhankoral 9e33581c66 Accept also detached @displayValue props 2023-05-29 18:05:29 +03:00
oguzhankoral b97792b596 Check only collection is model or not 2023-05-29 18:04:56 +03:00
Matteo Cominetti c0746f8eff Merge pull request #270 from specklesystems/oguzhan/hash-only-hostname-of-server-url 2023-05-25 18:17:37 +01:00
oguzhankoral a826a9d692 Hash hostname of the serverUrl 2023-05-25 20:09:32 +03:00
Oğuzhan Koral 6d04203d37 Feat (Collections): Eliminate relations for layer info 2023-05-24 15:57:47 +03:00
oguzhankoral 33b2ed8a94 Fix performance penalty 2023-05-24 14:38:27 +03:00
oguzhankoral 4f16da7ad0 Do not extract relations if from revit 2023-05-24 01:16:51 +03:00
oguzhankoral 36f92c7655 Add network object support 2023-05-24 01:00:06 +03:00
oguzhankoral 1d4f5a759e Eliminate relations for model collections 2023-05-23 23:31:02 +03:00
oguzhankoral 28af9bc811 Enable rescue block for to_native 2023-05-23 12:07:48 +03:00
oguzhankoral cf04cd4094 Check view up and direction parallel or not 2023-05-23 11:50:08 +03:00
oguzhankoral 23e9efb28a Pass layer prop through to_native methods 2023-05-23 11:49:51 +03:00
Oğuzhan Koral 57322df29c Fix (Layer): Do not send line_style if none 2023-05-22 13:57:23 +03:00
oguzhankoral fff82d34c6 Do not send line_style if none 2023-05-22 13:56:10 +03:00
Oğuzhan Koral 7211860c21 Chore (Layer): Support line styles for layers 2023-05-22 11:46:52 +03:00
oguzhankoral 9fc69044f8 Support line styles for layers 2023-05-22 11:42:51 +03:00
Oğuzhan Koral 76467c3e81 Fix (Blocks): Eliminate nil block geometries 2023-05-19 19:36:28 +03:00
oguzhankoral 4cfc04e2f3 Eliminate nil block geometries 2023-05-19 19:13:15 +03:00
Oğuzhan Koral 6bfc7771d0 Fix (Face): Merge coplanar doesn't work some cases 2023-05-19 12:26:24 +03:00
oguzhankoral 7055135257 Return nil if layer path nil 2023-05-19 12:20:55 +03:00
Oğuzhan Koral 85312cf20d Fix (Mapper): inconsistency on mapped elements table state 2023-05-16 13:36:27 +03:00
oguzhankoral 4051cae5b0 Clear cached element and category selection when mapped elements updated 2023-05-16 13:27:02 +03:00
oguzhankoral c64bb6dedf Include instances for button reactions
- Clear, Isolate, Hide, Select
2023-05-16 13:25:41 +03:00
Oğuzhan Koral 7bb17ae0db Fix (Collections): Add layer property to objects as full path 2023-05-16 08:39:06 +03:00
oguzhankoral 3244eb9b15 Add layer property to objects as full path
This is for receiving on Rhino
2023-05-16 08:33:42 +03:00
Oğuzhan Koral 6e173847ba Fix (Performance): Smooth only non-planar quad meshes 2023-05-15 13:53:56 +03:00
oguzhankoral 8cbc1f763d Smooth only non-planar quad meshes 2023-05-15 13:40:31 +03:00
Oğuzhan Koral 2d45abeb28 Release 2.14.0
Merging 2.13 hotfixes into dev branch
2023-05-15 13:17:54 +03:00
oguzhankoral 2fd0ad77bc Fix or disable rubocop issues 2023-05-15 13:14:32 +03:00
oguzhankoral 53c1167831 Remove orthogonality checks 2023-05-15 13:04:22 +03:00
oguzhankoral 48d4a1d7bb Add plane_utils to calculate planarity of 4 points for quad meshes 2023-05-15 13:04:19 +03:00
oguzhankoral 4bbbba6e89 Split all quad meshes 2023-05-15 13:03:22 +03:00
oguzhankoral 0a13575325 Disable rubocop for to_native 2023-05-15 13:02:12 +03:00
Oğuzhan Koral d6075209df Fix (Update): Check if branch instance is deleted 2023-05-12 12:53:19 +03:00
oguzhankoral 9d1b39422b Extract entities_updated method 2023-05-12 12:39:37 +03:00
oguzhankoral 38e297d32b Correct condition for is_update_commit 2023-05-12 11:31:23 +03:00
oguzhankoral 459ef3c9f1 Check if branch instance is deleted 2023-05-12 11:21:34 +03:00
Oğuzhan Koral 3bb12487ba Feat (QGIS): Attribute support for qgis commits 2023-05-12 10:13:44 +03:00
oguzhankoral 8b2428c60d Workaround for QGIS geometry objects or arrays 2023-05-10 17:26:47 +03:00
oguzhankoral f9befd9051 Write qgis attributes to components 2023-05-10 12:43:29 +03:00
oguzhankoral 1020a9bd5d Wrap PolygonElement object into component 2023-05-10 12:07:40 +03:00
Oğuzhan Koral 2844c4ea86 Fix (Instance): Disable aligning axes of displayValue components 2023-05-08 16:54:52 +03:00
oguzhankoral 3344188fee Disable aligning axes of displayValue components 2023-05-08 16:44:10 +03:00
Oğuzhan Koral 67138c2f78 Fix (Update): Update behavior for Revit > Sketchup 2023-05-08 13:21:31 +03:00
oguzhankoral f59d19ef21 Check children changed or not for upcoming blocks 2023-05-08 13:13:53 +03:00
Oğuzhan Koral d0fa7e638f Feat (Collections): Model collections 2023-05-08 09:46:44 +03:00
oguzhankoral 63011e466e Change relation speckle types to collections 2023-05-08 09:34:47 +03:00
oguzhankoral 92d1976300 Split collections into helper classes 2023-05-04 20:08:17 +03:00
oguzhankoral a0cdc9fa07 Check sketchup entities for respomding true to layer set, if so set 2023-05-04 12:22:02 +03:00
oguzhankoral 60f0006597 Remove old layer helper methods 2023-05-04 12:06:56 +03:00
oguzhankoral 67a3c62a08 Align instance axes that created from displayValues without transform 2023-05-03 18:23:58 +03:00
oguzhankoral 21d5dc1e0b Remove layer argument passing for all to_native methods 2023-04-27 17:59:39 +01:00
oguzhankoral 0d810f59f9 Place received objects into correct layers 2023-04-27 17:39:15 +01:00
oguzhankoral dba60b700f Layer relations to native layer tree 2023-04-27 17:21:20 +01:00
oguzhankoral ad9a56bd20 Remove unused methods 2023-04-27 16:16:15 +01:00
oguzhankoral 82585e9104 Convert views to native with traversal method 2023-04-27 16:14:20 +01:00
oguzhankoral dfbb241b0b Model collections on send 2023-04-27 14:56:59 +01:00
oguzhankoral f69ea91f45 Add query for layers 2023-04-26 16:44:11 +01:00
oguzhankoral 768d916092 Convert scenes to view3d on class 2023-04-26 16:44:00 +01:00
oguzhankoral c0acecae10 Query methods for direct shapes 2023-04-26 16:43:31 +01:00
oguzhankoral f741d1c7e4 Clean converter from object specific functions 2023-04-26 16:43:03 +01:00
oguzhankoral d25c70f1d1 Do not load bootstrap again when reload triggered 2023-04-26 16:42:06 +01:00
oguzhankoral 6f954467ef Move collections into related namespace 2023-04-26 16:41:37 +01:00
oguzhankoral 867987a5f5 Group meshes also according to layers 2023-04-26 14:39:52 +01:00
oguzhankoral 61e0a6351c Init collection class 2023-04-14 10:09:44 +03:00
oguzhankoral 558264c23b Add documentation to app methods 2023-04-13 13:57:37 +03:00
oguzhankoral cf7aa371af Move view related methods to related class 2023-04-12 20:04:17 +03:00
oguzhankoral ffedaf6a73 Fix bug on views receive 2023-04-12 14:02:50 +03:00
oguzhankoral d9ae272b36 Proof of concept of layer relations
# Conflicts:
#	speckle_connector/src/convertors/to_speckle.rb
#	speckle_connector/src/speckle_objects/geometry/mesh.rb
#	speckle_connector/src/speckle_objects/other/block_definition.rb
#	speckle_connector/src/speckle_objects/other/block_instance.rb
2023-04-12 12:56:59 +03:00
Oğuzhan Koral 3c5d1f5c9e Fix (References): missing namespaces 2023-04-12 11:09:03 +03:00
oguzhankoral b69b8b7585 Fix missing references after couple of implementation 2023-04-12 10:44:15 +03:00
Oğuzhan Koral 8c7e498fb2 Fix (Security): Bump sqlite3 from 5.0.8 to 5.1.5 in /ui 2023-04-10 14:42:56 +03:00
dependabot[bot] 359413a296 Bump sqlite3 from 5.0.8 to 5.1.5 in /ui
Bumps [sqlite3](https://github.com/TryGhost/node-sqlite3) from 5.0.8 to 5.1.5.
- [Release notes](https://github.com/TryGhost/node-sqlite3/releases)
- [Commits](https://github.com/TryGhost/node-sqlite3/compare/v5.0.8...v5.1.5)

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-04-10 11:12:22 +00:00
Oğuzhan Koral a6780a756b Feat (Mapper): Mapper tool for direct shapes 2023-04-10 14:05:44 +03:00
Oğuzhan Koral 4e37d17b42 Feat (Mapper): Mapped elements table and it's buttons 2023-04-10 13:48:19 +03:00
oguzhankoral 793923348f Switch button for isolate/show-all accoding to state 2023-04-10 13:37:04 +03:00
oguzhankoral 700db1788d Improve performance dramatically of isolate 2023-04-08 00:02:52 +03:00
oguzhankoral 68a5a44702 Make functional buttons for mapped elements 2023-04-07 20:16:00 +03:00
oguzhankoral 464891f5a1 Manage state of selected elements and existing elements 2023-04-07 11:33:05 +03:00
oguzhankoral d2e5db2680 Custom data table header for child tables 2023-04-06 22:59:46 +03:00
oguzhankoral a9c41d0545 Connect data relationship between parent and childeren data tables 2023-04-05 17:19:21 +03:00
oguzhankoral 0bc458d307 Connect nested data tables with their v-model 2023-04-05 13:54:05 +03:00
oguzhankoral 36c7cc285e Update vuetifyjs to 2.6.10 2023-04-05 13:52:56 +03:00
oguzhankoral f9fbd31a0f Wrap data table with container 2023-04-03 16:43:19 +03:00
oguzhankoral 8789c1f855 Extract mapped elements from mapper component 2023-04-03 15:41:04 +03:00
oguzhankoral f65073480a Apply mixed mapping name 2023-04-03 15:04:20 +03:00
oguzhankoral 157ed831a7 Shrink v-cards for map selection between component-definition 2023-03-31 23:19:19 +03:00
oguzhankoral 0799a59a15 Get info from definition about number of instances 2023-03-31 23:18:23 +03:00
Oğuzhan Koral 60ea3e83e1 Fix (DirectShape): Nested objects material 2023-03-31 19:57:46 +03:00
oguzhankoral 4b874deb50 Don't add mapped entities to layer groups 2023-03-31 19:55:12 +03:00
oguzhankoral 6726b9ad50 Fix nested material issıe 2023-03-31 19:54:29 +03:00
Oğuzhan Koral 75e6fe609e Fix (Mapper): Direct shape objects on viewer 2023-03-31 15:28:05 +03:00
oguzhankoral b40d1ccf7d Send directShapes collection as detached 2023-03-31 15:26:13 +03:00
Oğuzhan Koral 4f7ba23904 Feat (Mapper): Enable multiple mapping apply and clear 2023-03-31 14:33:41 +03:00
oguzhankoral 49084706f2 Enable multiple mapping apply and clear 2023-03-31 14:31:49 +03:00
Oğuzhan Koral 1852e5f6bf Chore (Mapper): Show default values for selected entity 2023-03-30 20:33:59 +03:00
oguzhankoral be0ed5428a Apply default values from selected entity 2023-03-30 20:31:16 +03:00
oguzhankoral f428efde6a Mark mapped cards and make active one 2023-03-30 19:56:49 +03:00
oguzhankoral e79f04f33e Remove unnecessary statement 2023-03-30 15:59:33 +03:00
oguzhankoral 04db8c281f Remove selected rows which was unneccessary 2023-03-30 15:59:03 +03:00
oguzhankoral 2681698186 Disable definition option for groups
- This is uncessary since each group has it's own unique definition on sketchup
2023-03-30 15:49:04 +03:00
Oğuzhan Koral 1682c66da6 Feat (Mapper): Read mapped elements on document load 2023-03-29 23:20:29 +03:00
oguzhankoral 38bc1032d8 Read mapped entities on document load model 2023-03-29 23:18:05 +03:00
Oğuzhan Koral ba37eef9ca Feat (Mapper): Group meshes also for base geometries of direct shapes 2023-03-29 22:49:30 +03:00
oguzhankoral 3db179882a Group direct shape meshes by material 2023-03-29 22:45:45 +03:00
oguzhankoral ba8f25a807 Move mesh related methods from definition 2023-03-29 22:44:06 +03:00
oguzhankoral eac3d3089b Find entities on active path 2023-03-29 22:43:13 +03:00
Oğuzhan Koral 0454a9e147 Fix (Mapper): Group global transformations for direct shapes 2023-03-29 21:03:13 +03:00
oguzhankoral f390cd1c68 Fix/disable rubocop issues 2023-03-29 21:02:49 +03:00
oguzhankoral aaa1ba5aa9 Apply parent's material to direct shape faces 2023-03-29 20:58:57 +03:00
oguzhankoral fb9556abd9 Fix sub elements transform 2023-03-29 20:58:35 +03:00
Oğuzhan Koral 3cfb3b1f84 Feat (Mapper): Show mapped elements on table 2023-03-29 14:54:21 +03:00
oguzhankoral b071bab137 Collect mapped entities when mapper mounted 2023-03-29 14:49:48 +03:00
oguzhankoral 5dbc68ea76 State management for mapped elements 2023-03-29 14:45:09 +03:00
oguzhankoral e76fbc3b77 Notify user after apply or clear mapping 2023-03-29 13:12:59 +03:00
oguzhankoral 09ca0514d1 Remove mapping word from buttons 2023-03-29 12:57:12 +03:00
oguzhankoral faec32a5bb Remove attribute dictionary completely if mappings cleared 2023-03-29 12:35:56 +03:00
oguzhankoral 1f5793ab79 Nil check for reading objects from scratch 2023-03-29 11:50:03 +03:00
oguzhankoral df22bd1cec Fallback to definition schema of instance 2023-03-29 11:47:21 +03:00
oguzhankoral 61dba4e78d Return false if entities are missing for base_and_entities 2023-03-29 11:44:58 +03:00
Oğuzhan Koral bb678117f8 Feat (Mapper): UI communication with Sketchup 2023-03-28 21:36:12 +03:00
oguzhankoral 2dc08e71b7 Collect also instances that definitions' mapped 2023-03-28 21:02:21 +03:00
oguzhankoral df2f844e74 Styles for entity selection to map 2023-03-28 20:11:52 +03:00
oguzhankoral 621c602fc0 Make narrower scrool bar 2023-03-28 20:11:15 +03:00
oguzhankoral f809169757 Fetch definition schema from selection 2023-03-28 20:10:59 +03:00
oguzhankoral 848b135612 Clear mappings 2023-03-28 20:10:26 +03:00
oguzhankoral db6af66705 Fill inputs according to selection 2023-03-28 02:34:55 +03:00
oguzhankoral 728e1f5a86 Send categories as key-value pair objects 2023-03-28 02:34:38 +03:00
oguzhankoral d02d95bc9e Apply mapping command and action 2023-03-28 02:34:13 +03:00
oguzhankoral c1180c5373 Trigger sketchup action to apply mappings 2023-03-28 01:49:17 +03:00
oguzhankoral 4e01fb64e6 Track objects from selection table and prepare mapping 2023-03-28 01:46:21 +03:00
oguzhankoral d0fcb1da34 Add theme colors for mapper active entity cards 2023-03-28 01:45:44 +03:00
oguzhankoral 48352d06b3 Extend selected entity data from sketchup 2023-03-28 01:45:18 +03:00
oguzhankoral d995652569 Get info from last selected entity 2023-03-27 18:57:42 +03:00
oguzhankoral 5a958fa51c Split class names with space 2023-03-27 18:57:13 +03:00
oguzhankoral cfb9e2cacd Print mapped count to selection table 2023-03-27 18:12:08 +03:00
oguzhankoral 7932fc1cab Send entity persistent_id to UI 2023-03-27 18:11:40 +03:00
oguzhankoral e0fc1715b5 Create data table for selection 2023-03-27 14:54:19 +03:00
oguzhankoral 1cfd6caa64 Read object schemas from selection with their type 2023-03-27 14:54:01 +03:00
oguzhankoral 54bf18888d Helper method definition to group objects by property value 2023-03-27 14:53:23 +03:00
Oğuzhan Koral 6b60fc4259 Feat (Observers): Selection observers to collect data 2023-03-24 19:37:20 +03:00
oguzhankoral 216af8697c Create accordion for mapper 2023-03-24 17:33:42 +03:00
oguzhankoral d97e7314d0 Add categories to fake data 2023-03-24 17:33:11 +03:00
oguzhankoral 67d9dda143 Sample interactions 2023-03-24 13:06:55 +03:00
oguzhankoral 5c60e303cb Move mapped_with_schema method to reader 2023-03-24 12:36:45 +03:00
oguzhankoral e36b8f3a56 Create selection observer to trigger UI 2023-03-24 12:36:21 +03:00
Oğuzhan Koral 1e7a19b463 Chore (UI): UI components for mapping tool 2023-03-24 12:30:55 +03:00
oguzhankoral 8004249a29 Init mapper with tab 2023-03-23 16:08:36 +03:00
Oğuzhan Koral 6cd800e4ef Feat (Mapping): Send direct shape PoC 2023-03-23 12:54:42 +03:00
oguzhankoral e182bc7ed2 Clean unused methods 2023-03-22 21:38:23 +03:00
oguzhankoral c338d51c9d Skip faces into definitions if it is mapped 2023-03-22 21:19:11 +03:00
oguzhankoral c1912250df Do definition check in advance and that's why definitions argument removed 2023-03-22 21:18:50 +03:00
oguzhankoral 994c41980e Init definitions and render_materials for later caching 2023-03-22 21:18:03 +03:00
oguzhankoral 385b7a514d Transform mesh if global_transformation defined 2023-03-22 21:17:25 +03:00
oguzhankoral 3aaade9228 Entity queries for parental relationships 2023-03-22 21:16:58 +03:00
oguzhankoral b4dd19b711 Split direct shape conversion from generic conversion 2023-03-22 21:06:53 +03:00
oguzhankoral 9e9b83b4ba Init direct shape definition 2023-03-22 21:06:14 +03:00
oguzhankoral 5ff5114669 Add revit categories dictionary 2023-03-21 19:55:44 +03:00
oguzhankoral f63039f3fb Change module name with revit specific 2023-03-21 19:53:47 +03:00
oguzhankoral b39399463d Add speckle schema to objects for to speckle 2023-03-21 14:17:36 +03:00
oguzhankoral 8217ef7fbb Create speckle schema dictionary handler 2023-03-21 14:17:18 +03:00
oguzhankoral b4851c34e1 Separate base dictionary_handler from root 2023-03-21 14:16:52 +03:00
107 changed files with 5504 additions and 674 deletions
+116 -2
View File
@@ -76,6 +76,99 @@ jobs:
paths:
- speckle-sharp-ci-tools/Installers
build-connector-mac:
macos:
xcode: 12.5.1
parameters:
projname:
type: string
default: ""
slug:
type: string
default: ""
installer:
type: boolean
default: false
converter-files:
type: string
default: ""
installername:
type: string
default: ""
build-config:
type: string
default: Release
bundlename:
type: string
default: ""
steps:
- checkout
- attach_workspace:
at: ./
- run:
name: Install dotnet
command: |
curl -sSL https://dot.net/v1/dotnet-install.sh | bash /dev/stdin --channel Current
$HOME/.dotnet/dotnet --version
$HOME/.dotnet/dotnet --list-runtimes
$HOME/.dotnet/dotnet --list-sdks
- run:
name: Create installer target dir
command: |
mkdir -p speckle-sharp-ci-tools/Installers/<< parameters.slug >>
- run:
name: Set Environment Variable
command: |
TAG=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "2.0.999"; fi;)
SEMVER=$(echo "$TAG" | sed -e 's/\/[a-zA-Z-]*//')
VER=$(echo "$SEMVER" | sed -e 's/-.*//')
VERSION=$(echo $VER.$WORKFLOW_NUM)
python3 patch_version.py $SEMVER
environment:
WORKFLOW_NUM: << pipeline.number >>
- run:
name: Zip Connector files
command: |
zip -r << parameters.slug >>-mac.zip "./speckle_connector" "./speckle_connector.rb"
# Copy installer files
- run:
name: Copy files to installer
command: |
mkdir -p speckle-sharp-ci-tools/Mac/<< parameters.installername >>/.installationFiles/
cp << parameters.slug >>-mac.zip speckle-sharp-ci-tools/Mac/<<parameters.installername>>/.installationFiles
# Create installer
- run:
name: Exit if External PR
command: if [ "$CIRCLE_PR_REPONAME" ]; then circleci-agent step halt; fi
- run:
name: Build Mac installer
command: ~/.dotnet/dotnet publish speckle-sharp-ci-tools/Mac/<<parameters.installername>>/<<parameters.installername>>.sln -r osx-x64 -c Release
- run:
name: Zip installer
command: |
cd speckle-sharp-ci-tools/Mac/<<parameters.installername>>/bin/Release/net6.0/osx-x64/publish/
zip -r <<parameters.slug>>.zip ./
- store_artifacts:
path: speckle-sharp-ci-tools/Mac/<<parameters.installername>>/bin/Release/net6.0/osx-x64/publish/<<parameters.slug>>.zip
- run:
name: Copy to installer location
command: |
TAG=$(if [ "${CIRCLE_TAG}" ]; then echo $CIRCLE_TAG; else echo "2.0.999"; fi;)
SEMVER=$(echo "$TAG" | sed -e 's/\/[a-zA-Z-]*//')
VER=$(echo "$SEMVER" | sed -e 's/-.*//')
VERSION=$(echo $VER.$WORKFLOW_NUM)
cp speckle-sharp-ci-tools/Mac/<<parameters.installername>>/bin/Release/net6.0/osx-x64/publish/<<parameters.slug>>.zip speckle-sharp-ci-tools/Installers/<< parameters.slug >>/<<parameters.slug>>-$SEMVER.zip
environment:
WORKFLOW_NUM: << pipeline.number >>
- when:
condition: << pipeline.git.tag >>
steps:
- persist_to_workspace:
root: ./
paths:
- speckle-sharp-ci-tools/Installers
get-ci-tools: # Clones our ci tools and persists them to the workspace
docker:
- image: cimg/base:2021.01
@@ -99,6 +192,7 @@ jobs:
root: ./
paths:
- speckle-sharp-ci-tools
deploy-manager2:
docker:
- image: mcr.microsoft.com/dotnet/sdk:6.0
@@ -146,17 +240,37 @@ workflows:
only: /.*/
context: innosetup
- build-connector-mac:
slug: sketchup
requires:
- get-ci-tools
- build-ui
filters:
tags:
only: /.*/
installername: SpeckleSketchUpInstall
- deploy-manager2:
context: do-spaces-speckle-releases
slug: sketchup
os: Win
extension: exe
requires:
- get-ci-tools
- build-ui
- build-connector
filters:
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
- deploy-manager2:
context: do-spaces-speckle-releases
slug: sketchup
os: OSX
extension: zip
requires:
- build-connector-mac
filters:
tags:
only: /([0-9]+)\.([0-9]+)\.([0-9]+)(?:-\w+)?$/
branches:
ignore: /.*/ # For testing only! /ci\/.*/
+3 -4
View File
@@ -1,7 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
addressable (2.8.1)
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
ast (2.4.2)
axiom-types (0.1.1)
@@ -26,7 +26,7 @@ GEM
path_expander (~> 1.0)
ruby_parser (~> 3.1, > 3.1.0)
sexp_processor (~> 4.8)
git (1.12.0)
git (1.18.0)
addressable (~> 2.8)
rchardet (~> 1.8)
ice_nine (0.11.2)
@@ -48,8 +48,7 @@ GEM
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
psych (3.3.4)
public_suffix (5.0.0)
public_suffix (5.0.1)
rainbow (3.1.1)
rake (13.0.6)
rchardet (1.8.0)
+1
View File
@@ -3,6 +3,7 @@
require 'sketchup'
require 'pathname'
require 'speckle_connector/debug'
require_relative 'src/log/log'
require_relative 'src/ui/sketchup_ui'
require_relative 'src/ui/ui_controller'
require_relative 'src/commands/menu_command_handler'
+3 -1
View File
@@ -15,7 +15,9 @@ module SpeckleConnector
def self.reload
load(__FILE__)
pattern = File.join(__dir__, '**/*.rb')
Dir.glob(pattern).each { |file| load(file) }
# TODO: Here is a opportunity to improve reloading process.
# We can cache last edited time of the each file later to check which file need to be reloaded.
Dir.glob(pattern).each { |file| load(file) unless file.include?('bootstrap') }
.size
end
# rubocop:enable SketchupSuggestions/FileEncoding
@@ -0,0 +1,73 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'mapped_entities_updated'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Apply mappings for selected entities.
class ApplyMappings < Action
def initialize(entities_to_map, method, category, family,
family_type, level, name, is_definition)
super()
@entities_to_map = entities_to_map
@method = method
@category = category
@name = name
@family = family
@family_type = family_type
@level = level
@is_definition = is_definition
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/MethodLength
def update_state(state)
sketchup_model = state.sketchup_state.sketchup_model
entities = if sketchup_model.active_path.nil?
sketchup_model.entities
else
sketchup_model.active_path.last.definition.entities
end
# Collect entities from entity ids that comes from UI as list
entities_to_map = entities.select { |e| @entities_to_map.include?(e.persistent_id) }
# Switch to definitions if all entities are component instance and UI flag shows that
if entities_to_map.all? { |e| e.is_a?(Sketchup::ComponentInstance) } && @is_definition
entities_to_map = entities_to_map.collect(&:definition).uniq
end
# Store speckle state to update with mapped entities.
speckle_state = state.speckle_state
entities_to_map.each do |entity|
name = if @name == '<Mixed>'
entity.respond_to?(:name) ? entity.name : ''
else
@name
end
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.set_attribute(entity, :category, @category)
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.set_attribute(entity, :name, name)
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.set_attribute(entity, :method, @method)
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.set_attribute(entity, :family, @family)
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.set_attribute(entity, :family_type, @family_type)
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.set_attribute(entity, :level, @level)
speckle_state = speckle_state.with_mapped_entity(entity)
end
new_state = MappedEntitiesUpdated.update_state(state.with_speckle_state(speckle_state))
Events::SelectionEventAction.update_state(new_state, { apply: true })
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/MethodLength
end
end
end
@@ -0,0 +1,31 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
module SpeckleConnector
module Actions
# Clear mapper source.
class ClearMapperSource < 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)
new_speckle_state = state.speckle_state.with_removed_mapper_source
erase_levels(state)
state.with_speckle_state(new_speckle_state)
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
def self.erase_levels(state)
levels = state.sketchup_state.sketchup_model.definitions.select do |definition|
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.get_attribute(definition, :speckle_type) ==
OBJECTS_BUILTELEMENTS_REVIT_LEVEL
end
levels.each do |level|
level.entities.clear!
level.instances.each(&:erase!)
end
end
end
end
end
@@ -0,0 +1,52 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'mapped_entities_updated'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Clear mappings for selected entities.
class ClearMappings < Action
def initialize(entities_to_map, is_definition)
super()
@entities_to_map = entities_to_map
@is_definition = is_definition
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def update_state(state)
sketchup_model = state.sketchup_state.sketchup_model
entities = if sketchup_model.active_path.nil?
sketchup_model.entities
else
sketchup_model.active_path.last.definition.entities
end
# Collect entities from entity ids that comes from UI as list
entities_to_map = entities.select { |e| @entities_to_map.include?(e.persistent_id) }
# Switch to definitions if all entities are component instance and UI flag shows that
if entities_to_map.all? { |e| e.is_a?(Sketchup::ComponentInstance) } && @is_definition
entities_to_map = entities_to_map.collect(&:definition).uniq
end
# Store speckle state to update with mapped entities.
speckle_state = state.speckle_state
entities_to_map.each do |entity|
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.remove_dictionary(entity)
speckle_state = speckle_state.with_removed_mapped_entity(entity)
end
new_state = MappedEntitiesUpdated.update_state(state.with_speckle_state(speckle_state))
Events::SelectionEventAction.update_state(new_state, { clear: true })
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
end
end
end
@@ -0,0 +1,34 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'mapped_entities_updated'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Clear mappings for selected entities from mapped elements table.
class ClearMappingsFromTable < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, data)
# Flat entities to clear mappings
flat_entities = SketchupModel::Query::Entity.flat_entities(state.sketchup_state.sketchup_model.entities)
# Collect entity ids to clear mappings
entity_ids = data.collect { |_, entities| entities['selectedElements'].collect { |e| e['entityId'] } }.flatten
# Store speckle state to update with mapped entities.
speckle_state = state.speckle_state
flat_entities.each do |entity|
next unless entity_ids.include?(entity.persistent_id)
SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.remove_dictionary(entity)
speckle_state = speckle_state.with_removed_mapped_entity(entity)
end
new_state = MappedEntitiesUpdated.update_state(state.with_speckle_state(speckle_state))
Events::SelectionEventAction.update_state(new_state, { clear: true })
end
end
end
end
@@ -0,0 +1,30 @@
# frozen_string_literal: true
require_relative 'event_action'
require_relative '../mapper_selection_changed'
require_relative '../../mapper/category/revit_category'
require_relative '../../sketchup_model/reader/speckle_entities_reader'
require_relative '../../sketchup_model/reader/mapper_reader'
require_relative '../../sketchup_model/query/entity'
module SpeckleConnector
module Actions
module Events
# Update selected speckle objects when the selection changes for mapper tool.
class SelectionEventAction < EventAction
# @param state [States::State] the current state of Speckle application.
# @return [States::State] the new updated state object
def self.update_state(state, event_data)
return state unless event_data&.any?
# Get sketchup selection
sketchup_selection = state.sketchup_state.sketchup_model.selection
# Collect and return mapper selection info.
# Later we can add more selection info for different scopes.
MapperSelectionChanged.new(sketchup_selection).update_state(state)
end
end
end
end
end
@@ -0,0 +1,36 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Hide entities that selected from mapped elements table.
class HideMappingsFromTable < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, data)
# Flat entities to clear mappings
flat_entities = SketchupModel::Query::Entity.flat_entities(state.sketchup_state.sketchup_model.entities)
# Collect entity ids to clear mappings
entity_ids = data.collect { |_, entities| entities['selectedElements'].collect { |e| e['entityId'] } }.flatten
# Store speckle state to update with mapped entities.
flat_entities.each do |entity|
next unless entity_ids.include?(entity.persistent_id)
if entity.is_a?(Sketchup::ComponentDefinition)
entity.instances.each do |instance|
instance.hidden = true
end
end
entity.hidden = true
end
Events::SelectionEventAction.update_state(state, { clear: true })
end
end
end
end
@@ -23,6 +23,7 @@ module SpeckleConnector
preferences = Preferences.read_preferences(sketchup_state.sketchup_model)
user_state_with_preferences = state.user_state.with_preferences(preferences)
state = States::State.new(user_state_with_preferences, speckle_state, sketchup_state, false)
# This is where we attach observers to related model objects like selection, entities..
Actions::LoadSketchupModel.update_state(state, sketchup_state.sketchup_model)
end
@@ -0,0 +1,64 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Isolate entities that selected from mapped elements table.
class IsolateMappingsFromTable < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/CyclomaticComplexity
def self.update_state(state, data)
sketchup_model = state.sketchup_state.sketchup_model
# Hide all entities first
sketchup_model.entities.each do |ent|
ent.hidden = true
end
# Flat entities to isolate mappings
flat_entities = SketchupModel::Query::Entity.flat_entities(sketchup_model.entities)
comp_flat_entities = flat_entities.grep(Sketchup::ComponentInstance) + flat_entities.grep(Sketchup::Group) +
flat_entities.grep(Sketchup::ComponentDefinition)
face_edge_flat_entities = flat_entities.grep(Sketchup::Face) + flat_entities.grep(Sketchup::Edge)
# Collect entity ids to clear mappings
selected_elements = data.collect { |_, entities| entities['selectedElements'] }.flatten
comps_or_groups, faces_or_edges = selected_elements.partition do |e|
e['entityType'] == 'Component' || e['entityType'] == 'Definition' || e['entityType'] == 'Group'
end
faces_or_edges_ids = faces_or_edges.collect { |e| e['entityId'] }
face_edge_flat_entities.select { |e| faces_or_edges_ids.include?(e.persistent_id) }.each do |entity|
entity.hidden = false
end
comps_or_groups_ids = comps_or_groups.collect { |e| e['entityId'] }
comp_flat_entities.select { |e| comps_or_groups_ids.include?(e.persistent_id) }.each do |entity|
if entity.is_a?(Sketchup::ComponentDefinition)
entity.instances.each do |instance|
instance.hidden = false
end
end
entity.hidden = false
end
Events::SelectionEventAction.update_state(state, { clear: true })
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
end
end
end
@@ -3,6 +3,7 @@
require_relative 'action'
require_relative 'initialize_materials'
require_relative '../sketchup_model/reader/speckle_entities_reader'
require_relative '../sketchup_model/reader/mapper_reader'
require_relative '../preferences/preferences'
require_relative '../states/state'
require_relative '../states/sketchup_state'
@@ -28,6 +29,9 @@ module SpeckleConnector
# Read speckle entities
new_speckle_entities = SketchupModel::Reader::SpeckleEntitiesReader.read(sketchup_model.entities)
new_speckle_state = new_state.speckle_state.with_speckle_entities(Immutable::Hash.new(new_speckle_entities))
# Read mapped entities
new_mapped_entities = SketchupModel::Reader::MapperReader.read_mapped_entities(sketchup_model.entities)
new_speckle_state = new_speckle_state.with_mapped_entities(Immutable::Hash.new(new_mapped_entities))
new_state = new_state.with_speckle_state(new_speckle_state)
# Read preferences from database and model.
@@ -42,8 +46,8 @@ module SpeckleConnector
# @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])
selection = sketchup_model.selection
selection.add_observer(observers[SELECTION_OBSERVER])
# layers = sketchup_model.layers
# layers.add_observer(observers[LAYERS_OBSERVER_NAME])
entities = sketchup_model.entities
@@ -0,0 +1,20 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../sketchup_model/reader/mapper_reader'
module SpeckleConnector
module Actions
# Triggers when mapped entities updated.
class MappedEntitiesUpdated < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data = nil)
mapped_entities = SketchupModel::Reader::MapperReader
.mapped_entity_details(state.speckle_state.speckle_mapper_state.mapped_entities.values.to_a)
state.with_mapped_entities_queue(mapped_entities)
end
end
end
end
@@ -0,0 +1,162 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../mapper/category/revit_category'
require_relative '../sketchup_model/reader/mapper_reader'
require_relative '../sketchup_model/reader/speckle_entities_reader'
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
module SpeckleConnector
module Actions
# Collects mapper selection info.
class MapperSelectionChanged < Action
READER = SketchupModel::Reader
DICTIONARY = SketchupModel::Dictionary
def initialize(selection)
super()
@selection = selection
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def update_state(state)
# Get mapping info from selection.
mapping = get_mapping_info(state, @selection)
state.with_mapper_selection_queue(mapping)
end
def filter_out_levels(selection)
selection.reject do |e|
DICTIONARY::SpeckleEntityDictionaryHandler
.get_attribute(e, :speckle_type) == OBJECTS_BUILTELEMENTS_REVIT_LEVEL
end
end
def get_mapping_info(state, selection)
selection = filter_out_levels(selection)
grouped_by_type = group_by_type(selection)
supported_entity_count = grouped_by_type.length
# Return empty method list if there is no supported entity to map.
return EMPTY_SELECTION if supported_entity_count == 0
# Return Direct Shape itself if multiple kinds of element are selected like Edge and Face.
# OR single type is equal to only direct shape supports.
if supported_entity_count > 1 ||
(supported_entity_count == 1 &&
MAPPER_DIRECT_SHAPE_SUPPORTED_ENTITY_TYPES.include?(grouped_by_type.keys.first))
return direct_shape_selection_info(selection)
end
# Only single type selections remained after this point.
return face_selection_info(state, grouped_by_type.values.first) if grouped_by_type.keys.first == Sketchup::Face
return edge_selection_info(state, grouped_by_type.values.first) if grouped_by_type.keys.first == Sketchup::Edge
EMPTY_SELECTION
end
MAPPER_SUPPORTED_ENTITY_TYPES = [
Sketchup::ComponentInstance,
Sketchup::Group,
Sketchup::Face,
Sketchup::Edge
].freeze
MAPPER_DIRECT_SHAPE_SUPPORTED_ENTITY_TYPES = [
Sketchup::ComponentInstance,
Sketchup::Group
].freeze
EMPTY_SELECTION = {
selection: [],
mappingMethods: [],
categories: []
}.freeze
def direct_shape_selection_info(selection)
{
selection: SketchupModel::Reader::MapperReader.entities_schema_details(selection),
mappingMethods: ['Direct Shape'],
categories: Mapper::Category::RevitCategory.to_a
}.freeze
end
def direct_shape_selection_info_with_default(selection, methods)
{
selection: SketchupModel::Reader::MapperReader.entities_schema_details(selection),
mappingMethods: ['Direct Shape'] + methods,
categories: Mapper::Category::RevitCategory.to_a
}.freeze
end
def direct_shape_selection_info_with_source(state, filtered_selection, methods)
types = state.speckle_state.speckle_mapper_state.mapper_source.types
levels = state.speckle_state.speckle_mapper_state.mapper_source.levels
instances = @selection.grep(Sketchup::ComponentInstance)
selected_level = instances.find do |i|
DICTIONARY::SpeckleEntityDictionaryHandler
.get_attribute(i, :speckle_type) == OBJECTS_BUILTELEMENTS_REVIT_LEVEL
end
selected_level_name = nil
if selected_level
selected_level_name = DICTIONARY::SpeckleEntityDictionaryHandler.get_attribute(selected_level, :name)
end
{
selection: READER::MapperReader.entities_schema_details(filtered_selection),
mappingMethods: ['Direct Shape'] + methods,
categories: Mapper::Category::RevitCategory.to_a,
types: types,
levels: levels,
selectedLevelName: selected_level_name
}.freeze
end
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
def face_selection_info(state, faces)
source_exist = !state.speckle_state.speckle_mapper_state.mapper_source.nil?
grouped_by_verticality = faces.group_by { |face| face.normal.perpendicular?(VECTOR_Z) }
return direct_shape_selection_info(faces) if grouped_by_verticality.length == 2
if source_exist
if grouped_by_verticality.keys.first
direct_shape_selection_info_with_source(state, faces, ['Wall'])
else
direct_shape_selection_info_with_source(state, faces, ['Floor'])
end
else
if grouped_by_verticality.keys.first
direct_shape_selection_info_with_default(faces, ['Default Wall'])
else
direct_shape_selection_info_with_default(faces, ['Default Floor'])
end
end
end
def edge_selection_info(state, edges)
source_exist = !state.speckle_state.speckle_mapper_state.mapper_source.nil?
if source_exist
methods = ['Column', 'Beam', 'Pipe', 'Duct']
direct_shape_selection_info_with_source(state, edges, methods)
else
default_methods = ['Default Column', 'Default Beam', 'Default Pipe', 'Default Duct']
direct_shape_selection_info_with_default(edges, default_methods)
end
end
def group_by_type_old(selection)
selection.group_by(&:class).filter_map do |group|
[group.first, group] if MAPPER_SUPPORTED_ENTITY_TYPES.include?(group.first)
end.to_h
end
def group_by_type(selection)
selection.select { |s| MAPPER_SUPPORTED_ENTITY_TYPES.include?(s.class) }.group_by(&:class)
end
end
end
end
@@ -0,0 +1,53 @@
# frozen_string_literal: true
require_relative 'action'
require_relative '../mapper/mapper_source'
require_relative '../speckle_objects/built_elements/revit/revit_element_type'
module SpeckleConnector
module Actions
# Action to update mapper source.
class MapperSourceUpdated < Action
def initialize(base, stream_id, commit_id)
super()
@base = base
@stream_id = stream_id
@commit_id = commit_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)
levels = convert_levels(state, @base['@Levels'])
types = convert_types(@base['@Types'])
mapper_source = Mapper::MapperSource.new(@stream_id, @commit_id, levels, types)
new_speckle_state = state.speckle_state.with_mapper_source(mapper_source)
state = state.with_speckle_state(new_speckle_state)
state.with_add_queue('mapperSourceUpdated', @stream_id, [
{ is_string: false, val: levels.to_json },
{ is_string: false, val: types.to_json }
])
end
def convert_types(types)
types.collect do |type, type_elements|
next if type_elements.nil? || !type_elements.is_a?(Array) || type == '__closure'
type = type[1..-1] if type[0] == '@'
elements = type_elements.map do |type_element|
SpeckleObjects::BuiltElements::Revit::RevitElementType.to_native(type_element)
end
elements = elements.group_by { |e| e[:family] }
[type, elements]
end.compact.to_h
end
def convert_levels(state, levels)
levels.collect do |level|
SpeckleObjects::BuiltElements::Level.to_native(state, level, @stream_id)
end
end
end
end
end
@@ -4,6 +4,7 @@ require_relative 'action'
require_relative 'events/app_event_action'
require_relative 'events/entities_event_action'
require_relative 'events/model_event_action'
require_relative 'events/selection_event_action'
require_relative '../constants/observer_constants'
module SpeckleConnector
@@ -13,11 +14,11 @@ module SpeckleConnector
RUN_ORDER = {
APP_OBSERVER => Events::AppEventAction,
ENTITIES_OBSERVER => Events::EntitiesEventAction,
MODEL_OBSERVER => Events::ModelEventAction
MODEL_OBSERVER => Events::ModelEventAction,
# MATERIALS_OBSERVER => Events::MaterialsEventAction,
# LAYERS_OBSERVER => Events::LayerEventAction,
# PAGES_OBSERVER => Events::PagesEventAction,
# SELECTION_OBSERVER => Events::SelectionEventAction
SELECTION_OBSERVER => Events::SelectionEventAction
}.freeze
def self.update_state(state, events)
@@ -3,6 +3,7 @@
require_relative 'action'
require_relative '../convertors/units'
require_relative '../convertors/to_native'
require_relative '../convertors/clean_up'
module SpeckleConnector
module Actions
@@ -26,7 +27,12 @@ module SpeckleConnector
converter = Converters::ToNative.new(state, @stream_id, @stream_name, @branch_name, @source_app)
# Have side effects on the sketchup model. It effects directly on the entities by adding new objects.
start_time = Time.now.to_f
state.sketchup_state.sketchup_model.start_operation('Receive Speckle Objects', true)
state = converter.receive_commit_object(@base)
if state.user_state.model_preferences[:merge_coplanar_faces]
Converters::CleanUp.merge_coplanar_faces(converter.converted_faces)
end
state.sketchup_state.sketchup_model.commit_operation
elapsed_time = (Time.now.to_f - start_time).round(3)
puts "==== Converting to Native executed in #{elapsed_time} sec ===="
puts "==== Source application is #{@source_app}. ===="
@@ -0,0 +1,37 @@
# frozen_string_literal: true
require_relative 'action'
require_relative 'events/selection_event_action'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module Actions
# Select entities that selected from mapped elements table.
class SelectMappingsFromTable < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, data)
# Clear first selection
state.sketchup_state.sketchup_model.selection.clear
# Flat entities to clear mappings
flat_entities = SketchupModel::Query::Entity.flat_entities(state.sketchup_state.sketchup_model.entities)
# Collect entity ids to clear mappings
entity_ids = data.collect { |_, entities| entities['selectedElements'].collect { |e| e['entityId'] } }.flatten
# Store speckle state to update with mapped entities.
flat_entities.each do |entity|
next unless entity_ids.include?(entity.persistent_id)
if entity.is_a?(Sketchup::ComponentDefinition)
state.sketchup_state.sketchup_model.selection.add(entity.instances)
end
state.sketchup_state.sketchup_model.selection.add(entity)
end
Events::SelectionEventAction.update_state(state, { clear: true })
end
end
end
end
@@ -0,0 +1,20 @@
# frozen_string_literal: true
require_relative 'action'
module SpeckleConnector
module Actions
# Show all entities on the model.
class ShowAllEntities < Action
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
# @return [States::State] the new updated state object
def self.update_state(state, _data)
# Show all entities first
state.sketchup_state.sketchup_model.entities.each do |ent|
ent.hidden = false
end
state
end
end
end
end
@@ -32,16 +32,20 @@ module SpeckleConnector
ui_controller.update_ui(state)
end
# Attach observers to application when speckle initialized via menu commands.
def add_observer_handler!(observer_handler)
@observer_handler = observer_handler
end
# Send messages to HtmlDialog if any.
def send_messages!
queue = @state.speckle_state.message_queue
queue.each_value { |value| ui_controller.user_interfaces[Ui::SPECKLE_UI_ID].dialog.execute_script(value) }
update_state!(Actions::ClearQueue)
end
def add_observer_handler!(observer_handler)
@observer_handler = observer_handler
end
# This is the only function application state will be switched by calling upcoming action with it's parameters
# if any.
def update_state!(action, *parameters)
old_state = @state
@state = action.update_state(old_state, *parameters)
@@ -0,0 +1,25 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/apply_mappings'
module SpeckleConnector
module Commands
# Command to apply mapping for selected entities.
class ApplyMappings < Command
def _run(data)
entities_to_map = data['entitiesToMap']
method = data['method']
category = data['category']
family = data['family']
family_type = data['familyType']
level = data['level']
name = data['name']
is_definition = data['isDefinition']
action = Actions::ApplyMappings.new(entities_to_map, method, category, family,
family_type, level, name, is_definition)
app.update_state!(action)
end
end
end
end
@@ -0,0 +1,18 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/clear_mappings'
module SpeckleConnector
module Commands
# Command to clear mapping for selected entities.
class ClearMappings < Command
def _run(data)
entities_to_map = data['entitiesToClearMap']
is_definition = data['isDefinition']
action = Actions::ClearMappings.new(entities_to_map, is_definition)
app.update_state!(action)
end
end
end
end
@@ -0,0 +1,19 @@
# frozen_string_literal: true
require_relative 'command'
require_relative '../actions/mapper_source_updated'
module SpeckleConnector
module Commands
# Command to update mapper source.
class MapperSourceUpdated < Command
def _run(data)
base = data['base']
stream_id = data['stream_id']
commit_id = data['commit_id']
action = Actions::MapperSourceUpdated.new(base, stream_id, commit_id)
app.update_state!(action)
end
end
end
end
@@ -2,6 +2,9 @@
module SpeckleConnector
SPECKLE_BASE_OBJECT = 'Speckle_Base_Object'
SPECKLE_MAPPING_TOOL_SCHEMA = 'Speckle_Mapping_Tool_Schema'
SPECKLE_SCHEMA = 'Speckle_Schema'
SPECKLE_ID = 'speckle_id'
SPECKLE_TYPE = 'speckle_type'
APPLICATION_ID = 'application_id'
@@ -0,0 +1,5 @@
# frozen_string_literal: true
module SpeckleConnector
VECTOR_Z = Geom::Vector3d.new(0, 0, 1)
end
@@ -4,4 +4,5 @@ module SpeckleConnector
APP_OBSERVER = 'SpeckleConnector::Observers::AppObserver'
ENTITIES_OBSERVER = 'SpeckleConnector::Observers::EntitiesObserver'
MODEL_OBSERVER = 'SpeckleConnector::Observers::ModelObserver'
SELECTION_OBSERVER = 'SpeckleConnector::Observers::SelectionObserver'
end
@@ -14,7 +14,7 @@ module SpeckleConnector
path = ENV.fetch('APPDATA')
Pathname.new(File.join(path, 'Speckle')).cleanpath.to_s
when OS_MAC
File.join(Dir.home, 'Library/Application Support/Speckle')
File.join(Dir.home, '.config/Speckle')
else
raise 'Speckle could not determine your Appdata path'
end
@@ -11,6 +11,13 @@ module SpeckleConnector
INCLUDE_COMPONENT_ENTITY_ATTRIBUTES = :include_component_entity_attributes
MERGE_COPLANAR_FACES = :merge_coplanar_faces
ENTITY_KEYS_FOR_INCLUDING_ATTRIBUTES = {
Sketchup::ComponentInstance => INCLUDE_COMPONENT_ENTITY_ATTRIBUTES,
Sketchup::Group => INCLUDE_GROUP_ENTITY_ATTRIBUTES,
Sketchup::Face => INCLUDE_FACE_ENTITY_ATTRIBUTES,
Sketchup::Face => INCLUDE_EDGE_ENTITY_ATTRIBUTES
}.freeze
LEVEL_SHIFT_VALUE = SpeckleObjects::Geometry.length_to_native(1.5, 'm')
DEFAULT_MODEL_PREFERENCES = {
@@ -3,10 +3,24 @@
module SpeckleConnector
BASE_OBJECT = 'Base'
OBJECTS_GIS_POLYGONELEMENT = 'Objects.GIS.PolygonElement'
OBJECTS_BUILTELEMENTS_VIEW3D = 'Objects.BuiltElements.View:Objects.BuiltElements.View3D'
OBJECTS_BUILTELEMENTS_NETWORK = 'Objects.BuiltElements.Network'
OBJECTS_BUILTELEMENTS_REVIT_LEVEL = 'Objects.BuiltElements.Level:Objects.BuiltElements.Revit.RevitLevel'
OBJECTS_BUILTELEMENTS_DEFAULT_FLOOR = 'Objects.BuiltElements.Floor'
OBJECTS_BUILTELEMENTS_REVIT_FLOOR = 'Objects.BuiltElements.Floor:Objects.BuiltElements.Revit.RevitFloor'
OBJECTS_BUILTELEMENTS_DEFAULT_WALL = 'Objects.BuiltElements.Wall'
OBJECTS_BUILTELEMENTS_REVIT_WALL = 'Objects.BuiltElements.Wall:Objects.BuiltElements.Revit.RevitWall'
OBJECTS_BUILTELEMENTS_REVIT_DIRECTSHAPE = 'Objects.BuiltElements.Revit.DirectShape'
OBJECTS_BUILTELEMENTS_REVIT_PARAMETER = 'Objects.BuiltElements.Revit.Parameter'
OBJECTS_BUILTELEMENTS_REVIT_REVITELEMENTTYPE = 'Objects.BuiltElements.Revit.RevitElementType'
OBJECTS_GEOMETRY_LINE = 'Objects.Geometry.Line'
OBJECTS_GEOMETRY_POLYLINE = 'Objects.Geometry.Polyline'
OBJECTS_GEOMETRY_POLYCURVE = 'Objects.Geometry.Polycurve'
OBJECTS_GEOMETRY_ARC = 'Objects.Geometry.Arc'
OBJECTS_GEOMETRY_CIRCLE = 'Objects.Geometry.Circle'
OBJECTS_GEOMETRY_MESH = 'Objects.Geometry.Mesh'
OBJECTS_GEOMETRY_BREP = 'Objects.Geometry.Brep'
@@ -16,4 +30,7 @@ module SpeckleConnector
OBJECTS_OTHER_REVIT_REVITINSTANCE = 'Objects.Other.Revit.RevitInstance'
OBJECTS_OTHER_BLOCKDEFINITION = 'Objects.Other.BlockDefinition'
OBJECTS_OTHER_RENDERMATERIAL = 'Objects.Other.RenderMaterial'
OBJECTS_OTHER_DISPLAYSTYLE = 'Objects.Other.DisplayStyle'
SPECKLE_CORE_MODELS_COLLECTION = 'Speckle.Core.Models.Collection'
end
@@ -228,6 +228,8 @@ module SpeckleConnector
return false unless is_array && value.length == 2
return false if value[1].nil?
value[1].all? { |v| v.is_a?(Sketchup::Entity) }
end
+24 -19
View File
@@ -11,12 +11,31 @@ module SpeckleConnector
# @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)
def self.merge_coplanar_entities(entities)
edges = []
faces = entities.collect { |entity| entity if entity.is_a? Sketchup::Face }.compact
faces = merged_faces(faces)
faces.each { |face| face.edges.each { |edge| edges << edge } }
edges.uniq!
edges.each { |edge| remove_edge_have_coplanar_faces(edge, faces, false) }
# Remove remaining orphan edges
edges.reject(&:deleted?).select { |edge| edge.faces.empty? }.each(&:erase!)
merged_faces(faces)
end
def self.merge_coplanar_faces(faces)
edges = []
faces = faces.reject(&:deleted?)
faces.each { |face| face.edges.each { |edge| edges << edge } }
edges.uniq!
edges.each { |edge| remove_edge_have_coplanar_faces(edge) }
# Remove remaining orphan edges
# edges.reject(&:deleted?).select { |edge| edge.faces.empty? }.each(&:erase!)
merged_faces(faces)
end
@@ -33,43 +52,29 @@ module SpeckleConnector
# - 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)
def self.remove_edge_have_coplanar_faces(edge)
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 unless face_1.normal.samedirection?(face_2.normal)
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)
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)
+127 -150
View File
@@ -2,16 +2,24 @@
require_relative 'converter'
require_relative '../constants/type_constants'
require_relative '../speckle_entities/speckle_entity'
require_relative '../speckle_objects/gis/polygon_element'
require_relative '../speckle_objects/other/transform'
require_relative '../speckle_objects/other/render_material'
require_relative '../speckle_objects/other/block_definition'
require_relative '../speckle_objects/other/block_instance'
require_relative '../speckle_objects/other/display_value'
require_relative '../speckle_objects/other/revit/revit_instance'
require_relative '../speckle_objects/revit/revit_instance'
require_relative '../speckle_objects/geometry/point'
require_relative '../speckle_objects/geometry/line'
require_relative '../speckle_objects/geometry/polycurve'
require_relative '../speckle_objects/geometry/arc'
require_relative '../speckle_objects/geometry/circle'
require_relative '../speckle_objects/geometry/mesh'
require_relative '../speckle_objects/built_elements/view3d'
require_relative '../speckle_objects/built_elements/network'
require_relative '../speckle_objects/speckle/core/models/collection'
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
module SpeckleConnector
module Converters
@@ -24,31 +32,47 @@ module SpeckleConnector
# @return [String] source application of received object that will be converted to native
attr_reader :source_app
attr_reader :converted_faces
def initialize(state, stream_id, stream_name, branch_name, source_app)
super(state, stream_id)
@stream_name = stream_name
@branch_name = branch_name
@source_app = source_app.downcase
@converted_faces = []
end
# Module aliases
GEOMETRY = SpeckleObjects::Geometry
OTHER = SpeckleObjects::Other
REVIT = SpeckleObjects::Revit
BUILTELEMENTS = SpeckleObjects::BuiltElements
GIS = SpeckleObjects::GIS
# Class aliases
POINT = GEOMETRY::Point
LINE = GEOMETRY::Line
POLYCURVE = GEOMETRY::Polycurve
ARC = GEOMETRY::Arc
CIRCLE = GEOMETRY::Circle
MESH = GEOMETRY::Mesh
BLOCK_DEFINITION = OTHER::BlockDefinition
BLOCK_INSTANCE = OTHER::BlockInstance
REVIT_INSTANCE = OTHER::Revit::RevitInstance
REVIT_INSTANCE = REVIT::Other::RevitInstance
REVIT_WALL = BUILTELEMENTS::RevitWall
RENDER_MATERIAL = OTHER::RenderMaterial
DISPLAY_VALUE = OTHER::DisplayValue
VIEW3D = BUILTELEMENTS::View3d
POLYGON_ELEMENT = GIS::PolygonElement
COLLECTION = SpeckleObjects::Speckle::Core::Models::Collection
BASE_OBJECT_PROPS = %w[applicationId id speckle_type totalChildrenCount].freeze
CONVERTABLE_SPECKLE_TYPES = %w[
Objects.Geometry.Line
Objects.Geometry.Polyline
Objects.Geometry.Polycurve
Objects.Geometry.Arc
Objects.Geometry.Circle
Objects.Geometry.Mesh
Objects.Geometry.Brep
Objects.Other.BlockInstance
@@ -56,44 +80,89 @@ module SpeckleConnector
Objects.Other.BlockDefinition
Objects.Other.RenderMaterial
Objects.Other.Instance:Objects.Other.BlockInstance
Objects.BuiltElements.View:Objects.BuiltElements.View3D
Objects.BuiltElements.Wall:Objects.BuiltElements.Revit.RevitWall
Objects.BuiltElements.Network
Objects.GIS.PolygonElement
Speckle.Core.Models.Collection
].freeze
def from_revit
@from_revit ||= source_app.include?('revit')
end
def from_rhino
@from_rhino ||= source_app.include?('rhino')
end
def from_sketchup
@from_sketchup ||= source_app.include?('sketchup')
end
def from_qgis
@from_qgis ||= source_app.include?('qgis')
end
# ReceiveObjects action call this method by giving everything that comes from server.
# Upcoming object is a referencedObject of selected commit to receive.
# UI is responsible currently to fetch objects from ObjectLoader module by calling getAndConstruct method.
# @param obj [Object] speckle commit object.
def receive_commit_object(obj)
# First create layers on the sketchup before starting traversing
# @Named Views are exception here. It does not mean a layer. But it is anti-pattern for now.
filtered_layer_containers = obj.keys.filter_map { |key| key if key.start_with?('@') && key != '@Named Views' }
create_layers(filtered_layer_containers, sketchup_model.layers) unless from_revit
# Convert views to sketchup scenes
SpeckleObjects::BuiltElements::View3d.to_native(obj, sketchup_model)
# Get default commit layer from sketchup model which will be used as fallback
unless from_revit
# Create layers and it's folders from layers relation on the model collection.
SpeckleObjects::Relations::Layers.to_native(obj, source_app, sketchup_model)
end
# By default entities to fill is sketchup model's entities.
@entities_to_fill = sketchup_model.entities
# Navigate to branch entities if commit doesn't come from sketchup
unless from_sketchup
@branch_definition = branch_definition
@entities_to_fill = @branch_definition.entities
end
default_commit_layer = sketchup_model.layers.layers.find { |layer| layer.display_name == '@Untagged' }
@entities_to_fill = entities_to_fill(obj)
traverse_commit_object(obj, sketchup_model.layers, default_commit_layer, @entities_to_fill)
traverse_commit_object(obj, default_commit_layer, @entities_to_fill)
create_levels_from_section_planes
check_hiding_layers_needed
try_create_instance
@state
end
# Creating instance from @branch_definition only available for non-sketchup commits since we wrap commits
# under instance.
# There is also another use case that maybe definition is exist in file but user might be deleted it before.
# If this is the case we can add instance by checking number of instances.
# rubocop:disable Style/GuardClause
def try_create_instance
if !from_sketchup && (!@is_update_commit || @branch_definition.instances.empty?)
instance = sketchup_model.entities.add_instance(@branch_definition, Geom::Transformation.new)
BLOCK_INSTANCE.align_instance_axes(instance) if from_qgis
end
end
# rubocop:enable Style/GuardClause
def levels_layer
@levels_layer ||= sketchup_model.layers.add('Levels')
end
def clear_levels
instances = @entities_to_fill.grep(Sketchup::ComponentInstance)
instances.each do |instance|
speckle_type = instance.get_attribute(SPECKLE_BASE_OBJECT, 'speckle_type')
next if speckle_type.nil?
sketchup_model.definitions.remove(instance.definition) if speckle_type == OBJECTS_BUILTELEMENTS_REVIT_LEVEL
end
end
# Create levels from section planes that already created for this commit object.
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/MethodLength
def create_levels_from_section_planes
clear_levels if @is_update_commit
return unless from_revit
section_planes = @entities_to_fill.grep(Sketchup::SectionPlane)
@@ -105,7 +174,9 @@ module SpeckleConnector
section_planes.each do |section_plane|
level_name = "#{@definition_name}-#{section_plane.name}"
definition = sketchup_model.definitions.add(level_name)
@entities_to_fill.add_instance(definition, Geom::Transformation.new)
instance = @entities_to_fill.add_instance(definition, Geom::Transformation.new)
att = section_plane.attribute_dictionary(SPECKLE_BASE_OBJECT).to_h
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.set_hash(instance, att)
elevation = section_plane.bounds.center.z
c1_e = Geom::Point3d.new(c_1.x, c_1.y, elevation - LEVEL_SHIFT_VALUE)
c2_e = Geom::Point3d.new(c_2.x, c_2.y, elevation - LEVEL_SHIFT_VALUE)
@@ -122,8 +193,17 @@ module SpeckleConnector
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/MethodLength
# @return [Sketchup::ComponentDefinition] branch definition to fill objects in it.
def branch_definition
@definition_name = "#{@branch_name}-#{@stream_name}"
definition = sketchup_model.definitions.find { |d| d.name == @definition_name }
@is_update_commit = !definition.nil?
definition = sketchup_model.definitions.add(@definition_name) if definition.nil?
definition
end
def entities_to_fill(_obj)
return sketchup_model.entities if from_sketchup
return sketchup_model.entities unless from_revit
@definition_name = "#{@branch_name}-#{@stream_name}"
definition = sketchup_model.definitions.find { |d| d.name == @definition_name }
@@ -131,7 +211,7 @@ module SpeckleConnector
definition = sketchup_model.definitions.add(@definition_name)
sketchup_model.entities.add_instance(definition, Geom::Transformation.new)
end
definition.entities
definition
end
LAYERS_WILL_BE_HIDDEN = [
@@ -169,82 +249,14 @@ module SpeckleConnector
['Objects.BuiltElements.Revit.Parameter'].include?(obj['speckle_type'])
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 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, entities)
def traverse_commit_object(obj, layer, entities)
if convertible_to_native?(obj)
@state = convert_to_native(@state, obj, layer, entities)
@state, _converted_entities = convert_to_native(@state, obj, layer, entities)
elsif obj.is_a?(Hash) && obj.key?('speckle_type')
return if ignored_speckle_type?(obj)
@@ -252,57 +264,23 @@ module SpeckleConnector
# puts(">>> Found #{obj['speckle_type']}: #{obj['id']}. Continuing traversal.")
props = obj.keys.filter_map { |key| key unless key.start_with?('_') }
props.each do |prop|
layer_path = prop if prop.start_with?('@') && obj[prop].is_a?(Array)
layer = find_layer(layer_path, commit_folder, layer)
traverse_commit_object(obj[prop], commit_folder, layer, entities)
traverse_commit_object(obj[prop], layer, entities)
end
else
# puts(">>> Found #{obj['speckle_type']}: #{obj['id']} with displayValue.")
@state = convert_to_native(@state, obj, layer, entities)
@state, _converted_entities = convert_to_native(@state, obj, layer, entities)
end
elsif obj.is_a?(Hash)
obj.each_value { |value| traverse_commit_object(value, commit_folder, layer, entities) }
obj.each_value { |value| traverse_commit_object(value, layer, entities) }
elsif obj.is_a?(Array)
obj.each { |value| traverse_commit_object(value, commit_folder, layer, entities) }
obj.each { |value| traverse_commit_object(value, layer, entities) }
end
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# Find layer of the Speckle object by checking iteratively into folder.
# @param layer_path [String] complete layer_path to retrieve
# @param folder [Sketchup::LayerFolder, Sketchup::Layers] entry folder to search layer
# @param fallback_layer [Sketchup::Layer] fallback layer to assign object later if any error occur.
# @return [Sketchup::Layer] layer according to path
# @example
# "@folder_1::folder_2::layer_1"
# # it will return the layer object which has display name as `layer_1`.
def find_layer(layer_path, folder, fallback_layer)
begin
# Split folders and it's tail layer (last one is layer, others are folders.)
layer_path_array = layer_path[1..-1].split('::')
# Get sub folders as array, might be empty if `layer_path_array` has only 1 entry
sub_folders = layer_path_array.length > 1 ? layer_path_array[0..-2] : []
# Get exact layer name from last entry
layer_name = layer_path_array.last
# Iterate sub folders to find new sub folder to switch it.
# It help to search in the tree by switching the target search folder.
# Finally we can reach the layer name.
sub_folders.each do |sub_folder|
# Try to find sub folder into source folder passes by argument
s_f = folder.folders.find { |f| f.display_name == sub_folder }
# Switch source folder if any exist
folder = s_f unless s_f.nil?
end
# Find finally the layer into related folder
folder.layers.find { |l| l.display_name == layer_name }
rescue StandardError
return fallback_layer
end
end
def speckle_object_to_native(obj)
return DISPLAY_VALUE.method(:to_native) unless obj['displayValue'].nil?
return DISPLAY_VALUE.method(:to_native) unless obj['displayValue'].nil? && obj['@displayValue'].nil?
SPECKLE_OBJECT_TO_NATIVE[obj['speckle_type']]
end
@@ -310,13 +288,22 @@ module SpeckleConnector
SPECKLE_OBJECT_TO_NATIVE = {
OBJECTS_GEOMETRY_LINE => LINE.method(:to_native),
OBJECTS_GEOMETRY_POLYLINE => LINE.method(:to_native),
OBJECTS_GEOMETRY_POLYCURVE => POLYCURVE.method(:to_native),
OBJECTS_GEOMETRY_ARC => ARC.method(:to_native),
OBJECTS_GEOMETRY_CIRCLE => CIRCLE.method(:to_native),
OBJECTS_GEOMETRY_MESH => MESH.method(:to_native),
OBJECTS_GEOMETRY_BREP => MESH.method(:to_native),
OBJECTS_OTHER_BLOCKDEFINITION => BLOCK_DEFINITION.method(:to_native),
OBJECTS_OTHER_BLOCKINSTANCE => BLOCK_INSTANCE.method(:to_native),
OBJECTS_OTHER_BLOCKINSTANCE_FULL => BLOCK_INSTANCE.method(:to_native),
OBJECTS_OTHER_REVIT_REVITINSTANCE => REVIT_INSTANCE.method(:to_native),
OBJECTS_OTHER_RENDERMATERIAL => RENDER_MATERIAL.method(:to_native)
OBJECTS_OTHER_RENDERMATERIAL => RENDER_MATERIAL.method(:to_native),
OBJECTS_BUILTELEMENTS_VIEW3D => VIEW3D.method(:to_native),
OBJECTS_BUILTELEMENTS_REVIT_WALL => REVIT_WALL.method(:to_native),
OBJECTS_BUILTELEMENTS_REVIT_DIRECTSHAPE => BUILTELEMENTS::Revit::DirectShape.method(:to_native),
OBJECTS_BUILTELEMENTS_NETWORK => BUILTELEMENTS::Network.method(:to_native),
OBJECTS_GIS_POLYGONELEMENT => POLYGON_ELEMENT.method(:to_native),
SPECKLE_CORE_MODELS_COLLECTION => COLLECTION.method(:to_native)
}.freeze
# @param state [States::State] state of the speckle application
@@ -328,6 +315,8 @@ module SpeckleConnector
# Call 'to_native' method by passing this method itself to handle nested 'to_native' conversions.
# It returns updated state and converted entities.
state, converted_entities = to_native_method.call(state, obj, layer, entities, &convert_to_native)
faces = converted_entities.select { |e| e.is_a?(Sketchup::Face) }
@converted_faces += faces if faces.any?
if from_revit
# Create levels as section planes if they exists
create_levels(state, obj)
@@ -335,11 +324,11 @@ module SpeckleConnector
create_layers_from_categories(state, obj, converted_entities)
end
# Create speckle entities from sketchup entities to achieve continuous traversal.
convert_to_speckle_entities(state, obj, converted_entities)
SpeckleEntities::SpeckleEntity.from_speckle_object(state, obj, converted_entities, stream_id)
rescue StandardError => e
puts("Failed to convert #{obj['speckle_type']} (id: #{obj['id']})")
puts(e)
return state
return state, []
end
# rubocop:disable Metrics/CyclomaticComplexity
@@ -349,13 +338,13 @@ module SpeckleConnector
layer = sketchup_model.layers.find { |l| l.display_name == speckle_object['category'] }
unless layer.nil?
entities.each { |entity| entity.layer = layer } if layer
entities.each { |entity| entity.layer = layer if entity.respond_to?(:layer) } if layer
return state
end
layer = sketchup_model.layers.add(speckle_object['category'])
unless layer.nil?
entities.each { |entity| entity.layer = layer } if layer
entities.each { |entity| entity.layer = layer if entity.respond_to?(:layer) } if layer
state
end
state
@@ -365,39 +354,27 @@ module SpeckleConnector
# @param state [States::State] state of the speckle application
def create_levels(state, speckle_object)
return state if speckle_object['level'].nil?
return state unless speckle_object['level']['speckle_type'].include?('Objects.BuiltElements.Level')
level = speckle_object['level']
return state if level.nil?
return state unless level['speckle_type'].include?('Objects.BuiltElements.Level')
level_name = speckle_object['level']['name'] || speckle_object['level']['id']
level_name = level['name'] || level['id']
is_exist = @entities_to_fill.grep(Sketchup::SectionPlane).any? { |sp| sp.name == level_name }
return state if is_exist
elevation = SpeckleObjects::Geometry.length_to_native(speckle_object['level']['elevation'],
speckle_object['level']['units'])
elevation = SpeckleObjects::Geometry.length_to_native(level['elevation'], level['units'])
section_plane = @entities_to_fill.add_section_plane([0, 0, elevation + LEVEL_SHIFT_VALUE], [0, 0, -1])
section_plane.name = level_name
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.write_initial_base_data(
section_plane, level['applicationId'], level['id'], level['speckle_type'], [], @stream_id
)
state
end
# @param state [States::State] state of the application
def convert_to_speckle_entities(state, speckle_object, entities)
speckle_id = speckle_object['id']
application_id = speckle_object['applicationId']
speckle_type = speckle_object['speckle_type']
children = speckle_object['__closure'].nil? ? [] : speckle_object['__closure']
speckle_state = state.speckle_state
entities.each do |entity|
next if entity.is_a?(Sketchup::Material)
next if (entity.is_a?(Sketchup::Face) || entity.is_a?(Sketchup::Edge)) &&
!state.user_state.user_preferences[:register_speckle_entity]
ent = SpeckleEntities::SpeckleEntity.new(entity, speckle_id, application_id, speckle_type, children,
[stream_id])
ent.write_initial_base_data
speckle_state = speckle_state.with_speckle_entity(ent)
end
state.with_speckle_state(speckle_state)
def convert_to_speckle_entities(state, speckle_objects_with_entities)
return state if speckle_objects_with_entities.empty?
end
end
# rubocop:enable Metrics/ClassLength
+37 -141
View File
@@ -10,63 +10,31 @@ require_relative '../speckle_objects/other/block_instance'
require_relative '../speckle_objects/other/block_definition'
require_relative '../speckle_objects/other/rendering_options'
require_relative '../speckle_objects/built_elements/view3d'
require_relative '../speckle_objects/built_elements/revit/direct_shape'
require_relative '../speckle_objects/relations/layers'
require_relative '../speckle_objects/speckle/core/models/model_collection'
require_relative '../constants/path_constants'
require_relative '../sketchup_model/reader/speckle_entities_reader'
require_relative '../sketchup_model/reader/mapper_reader'
require_relative '../sketchup_model/query/entity'
module SpeckleConnector
module Converters
# Converts sketchup entities to speckle objects.
class ToSpeckle < Converter
MODEL_COLLECTION = SpeckleObjects::Speckle::Core::Models::ModelCollection
DIRECT_SHAPE = SpeckleObjects::BuiltElements::Revit::DirectShape
SPECKLE_ENTITIES_READER = SketchupModel::Reader::SpeckleEntitiesReader
VIEW3D = SpeckleObjects::BuiltElements::View3d
# Convert selected objects by putting them into related array that grouped by layer.
# @return [Hash{Symbol=>Array}] layers -which only have objects- to hold it's objects under the base object.
def convert_selection_to_base(preferences)
layers = add_all_layers
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
convert = method(:convert)
new_speckle_state, model_collection = MODEL_COLLECTION.from_sketchup_model(sketchup_model, speckle_state,
@units, preferences, &convert)
# 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?
}
return new_speckle_state, model_collection
end
# Serialized and traversed information to send batches.
@@ -93,17 +61,32 @@ module SpeckleConnector
# @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)
unless SketchupModel::Reader::MapperReader.mapped_with_schema?(entity)
return from_native_to_speckle(entity, preferences, speckle_state, parent, &convert)
end
return speckle_state, nil
end
def from_mapped_to_speckle(entity, path, preferences)
direct_shape = SpeckleObjects::BuiltElements::Revit::DirectShape
.from_entity(speckle_state, entity, path, @units, preferences)
return [direct_shape, [entity]]
end
# rubocop:disable Metrics/MethodLength
def from_native_to_speckle(entity, preferences, speckle_state, parent, &convert)
if entity.is_a?(Sketchup::Edge)
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])
mesh = SpeckleObjects::Geometry::Mesh.from_face(speckle_state: speckle_state, face: entity, units: @units,
model_preferences: preferences[:model])
return speckle_state, [mesh, [entity]]
end
@@ -124,9 +107,13 @@ module SpeckleConnector
end
if entity.is_a?(Sketchup::ComponentDefinition)
# Local caching
return speckle_state, [definitions[entity.guid], [entity]] if definitions.key?(entity.guid)
new_speckle_state, block_definition = SpeckleObjects::Other::BlockDefinition.from_definition(
entity, @units, @definitions, preferences, speckle_state, parent, &convert
entity, @units, preferences, speckle_state, parent, &convert
)
definitions[entity.guid] = block_definition
speckle_state = new_speckle_state
return speckle_state, [block_definition, [entity]]
end
@@ -134,97 +121,6 @@ module SpeckleConnector
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
Binary file not shown.
+12
View File
@@ -0,0 +1,12 @@
# frozen_string_literal: true
module SpeckleConnector
# Helper module for logging.
module Log
def self.write_to_file(text, file_name = 'log', path = "#{ENV['HOME']}/Desktop")
file_path = path + "/#{file_name}.json"
File.delete(file_path) if File.exist?(file_path)
File.write(file_path, text)
end
end
end
@@ -0,0 +1,141 @@
# frozen_string_literal: true
module SpeckleConnector
module Mapper
module Category
# Revit categories.
class RevitCategory < Hash
class << self
# rubocop:disable Metrics/MethodLength
def dictionary
{
AbutmentFoundations: 0,
AbutmentPiles: 1,
AbutmentWalls: 2,
BridgeAbutments: 3,
DuctTerminal: 4,
Alignments: 5,
StructConnectionAnchors: 6,
ApproachSlabs: 7,
BridgeArches: 8,
AudioVisualDevices: 9,
StairsRailingBaluster: 10,
BridgeBearings: 11,
StructConnectionBolts: 12,
BridgeCables: 13,
BridgeDecks: 14,
BridgeFraming: 15,
CableTrayFitting: 16,
CableTrayRun: 17,
CableTray: 18,
Casework: 19,
Ceilings: 20,
Columns: 21,
CommunicationDevices: 22,
ConduitFitting: 23,
Conduit: 24,
Coordination_Model: 25,
BridgeFramingCrossBracing: 26,
CurtainWallPanels: 27,
CurtaSystem: 28,
CurtainWallMullions: 29,
DataDevices: 30,
BridgeFramingDiaphragms: 31,
Doors: 32,
DuctAccessory: 33,
DuctFitting: 34,
PlaceHolderDucts: 35,
DuctSystem: 36,
DuctCurves: 37,
ElectricalEquipment: 38,
ElectricalFixtures: 39,
Entourage: 40,
ExpansionJoints: 41,
FireAlarmDevices: 42,
FireProtection: 43,
Floors: 44,
FoodServiceEquipment: 45,
Furniture: 46,
FurnitureSystems: 47,
GenericAnnotation: 48,
GenericModel: 49,
BridgeGirders: 50,
Hardscape: 51,
LightingDevices: 52,
LightingFixtures: 53,
Lines: 54,
Mass: 55,
MechanicalEquipment: 56,
MedicalEquipment: 57,
NurseCallDevices: 58,
Parking: 59,
Parts: 60,
PierCaps: 61,
PierColumns: 62,
BridgeFoundations: 63,
PierPiles: 64,
BridgeTowers: 65,
PierWalls: 66,
BridgePiers: 67,
PipeAccessory: 68,
PipeFitting: 69,
PlaceHolderPipes: 70,
PipeSegments: 71,
PipeCurves: 72,
PipingSystem: 73,
Planting: 74,
StructConnectionPlates: 75,
PlumbingFixtures: 76,
StructConnectionProfiles: 77,
StairsRailing: 78,
Ramps: 79,
Roads: 80,
Roofs: 81,
SecurityDevices: 82,
StructConnectionShearStuds: 83,
Signage: 84,
Site: 85,
SpecialityEquipment: 86,
Sprinklers: 87,
Stairs: 88,
StructuralFramingSystem: 89,
StructuralColumns: 90,
StructConnections: 91,
FabricAreas: 92,
StructuralFoundation: 93,
StructuralFraming: 94,
Rebar: 95,
Coupler: 96,
StructuralStiffener: 97,
StructuralTendons: 98,
StructuralTruss: 99,
TemporaryStructure: 100,
Topography: 101,
BridgeFramingTrusses: 102,
VerticalCirculation: 103,
VibrationDampers: 104,
VibrationIsolators: 105,
VibrationManagement: 106,
Walls: 107,
StructConnectionWelds: 108,
Windows: 109
}.freeze
end
# rubocop:enable Metrics/MethodLength
def reverse_dictionary
dictionary.collect { |k, v| [v, k] }.to_h
end
def to_a
dictionary.collect { |k, v| { key: k, value: v } }.to_a
end
def reverse_to_a
dictionary.collect { |k, v| { key: v, value: k } }.to_a
end
end
end
end
end
end
+61
View File
@@ -0,0 +1,61 @@
# frozen_string_literal: true
require_relative '../speckle_objects/built_elements/revit/revit_floor'
require_relative '../speckle_objects/built_elements/revit/revit_wall'
require_relative '../speckle_objects/built_elements/default_floor'
require_relative '../speckle_objects/built_elements/default_wall'
require_relative '../sketchup_model/query/entity'
require_relative '../sketchup_model/reader/mapper_reader'
require_relative '../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
# Mapper is a tool to convert SketchUp entities to other applications' native objects.
module Mapper
# Collects mapped entities on selection as flat list.
def self.mapped_entities_on_selection(sketchup_model)
flat_selection_with_path = SketchupModel::Query::Entity.flat_entities_with_path(
sketchup_model.selection,
[Sketchup::Face, Sketchup::ComponentInstance, Sketchup::Group], [sketchup_model]
)
mapped_selection = []
flat_selection_with_path.each do |entities|
entity = entities[0]
is_entity_mapped = SketchupModel::Reader::MapperReader.mapped_with_schema?(entity)
if entity.respond_to?(:definition)
is_definition_mapped = SketchupModel::Reader::MapperReader.mapped_with_schema?(entity.definition)
mapped_selection.append(entities) if is_entity_mapped || is_definition_mapped
next
end
mapped_selection.append(entities) if is_entity_mapped
end
mapped_selection
end
def self.to_speckle(speckle_state, entity, units, global_transformation: nil)
speckle_schema = SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.speckle_schema_to_speckle(entity)
return speckle_schema if speckle_schema.nil?
if speckle_schema['method'] == 'Default Floor'
return SpeckleObjects::BuiltElements::DefaultFloor
.to_speckle_schema(entity, units, global_transformation: global_transformation)
end
if speckle_schema['method'] == 'Floor'
return SpeckleObjects::BuiltElements::RevitFloor
.to_speckle_schema(speckle_state, entity, units, global_transformation: global_transformation)
end
if speckle_schema['method'] == 'Default Wall'
return SpeckleObjects::BuiltElements::DefaultWall
.to_speckle_schema(entity, units, global_transformation: global_transformation)
end
if speckle_schema['method'] == 'Wall'
return SpeckleObjects::BuiltElements::RevitWall
.to_speckle_schema(speckle_state, entity, units, global_transformation: global_transformation)
end
return speckle_schema
end
end
end
@@ -0,0 +1,34 @@
# frozen_string_literal: true
require_relative '../immutable/immutable'
require_relative '../speckle_objects/built_elements/level'
require_relative '../speckle_objects/built_elements/revit/revit_element_type'
module SpeckleConnector
# Mapper is a tool to convert SketchUp entities to other applications' native objects.
module Mapper
# Mapper source object that collects information about stream id and commit id to identify source in the branch,
# also contains levels and family types to be able to map objects with them.
class MapperSource
# @return [String] stream id of the mapper source.
attr_reader :stream_id
# @return [String] commit id of the mapper source.
attr_reader :commit_id
# @return [Array<SpeckleObjects::BuiltElements::Level>] levels in the source branch.
attr_reader :levels
# @return [ImmutableHash{String=>Array<SpeckleObjects::BuiltElements::Revit::RevitElementType>}] revit element
# types.
attr_reader :types
def initialize(stream_id, commit_id, levels, types)
@stream_id = stream_id
@commit_id = commit_id
@levels = levels
@types = types
end
end
end
end
+3 -1
View File
@@ -5,6 +5,7 @@ require_relative 'entities_observer'
require_relative 'observer_handler'
require_relative 'model_observer'
require_relative 'event_handler'
require_relative 'selection_observer'
require_relative '../constants/observer_constants'
module SpeckleConnector
@@ -22,7 +23,8 @@ module SpeckleConnector
{
APP_OBSERVER => AppObserver.new(handler),
ENTITIES_OBSERVER => EntitiesObserver.new(handler),
MODEL_OBSERVER => ModelObserver.new(handler)
MODEL_OBSERVER => ModelObserver.new(handler),
SELECTION_OBSERVER => SelectionObserver.new(handler)
}.freeze
end
end
@@ -0,0 +1,49 @@
# frozen_string_literal: true
require_relative 'event_observer'
module SpeckleConnector
module Observers
# @see https://ruby.sketchup.com/Sketchup/SelectionObserver.html
class SelectionObserver < Sketchup::SelectionObserver
include EventObserver
# rubocop:disable Naming/MethodName
# @param _selection (Sketchup::Selection)
# @param _entity (Sketchup::Entity)
def onSelectionAdded(_selection, _entity)
push_selection_event(:onSelectionAdded)
end
# @param _selection (Sketchup::Selection)
def onSelectionBulkChange(_selection)
push_selection_event(:onSelectionBulkChange)
end
# @param _selection (Sketchup::Selection)
def onSelectionCleared(_selection)
push_selection_event(:onSelectionCleared)
end
# @param _selection (Sketchup::Selection)
def onSelectionRemoved(_selection, _entity)
push_selection_event(:onSelectionRemoved)
end
# Due to a SketchUp bug, this method is called by the wrong name.
alias onSelectedRemoved onSelectionRemoved
# rubocop:enable Naming/MethodName
private
# Selection changes need to be registered only once
def push_selection_event(event_name)
# Don't push anything if the selection event was already registered
selection_events = observer_handler.events[self.class]
return if selection_events&.any?
push_event(event_name)
end
end
end
end
@@ -12,9 +12,9 @@ module SpeckleConnector
include Immutable::ImmutableUtils
DICT_HANDLER = SketchupModel::Dictionary::SpeckleModelDictionaryHandler
# rubocop:disable Layout/LineLength
DEFAULT_CONFIG = "('configSketchup', '{\"dark_theme\":false, \"diffing\":false, \"register_speckle_entity\":false}');"
DEFAULT_CONFIG = "('configSketchup', '{\"dark_theme\":false, \"diffing\":false, \"register_speckle_entity\":false, \"fe2\":false}');"
# rubocop:enable Layout/LineLength
DEFAULT_PREFERENCES = '{"dark_theme":false, "diffing":false, "register_speckle_entity": false}'
DEFAULT_PREFERENCES = '{"dark_theme":false, "diffing":false, "register_speckle_entity": false, "fe2": false}'
# @param sketchup_model [Sketchup::Model] active model.
def self.read_preferences(sketchup_model)
@@ -34,10 +34,16 @@ module SpeckleConnector
def self.data_complete?(row_data)
return false if row_data.empty?
data = JSON.parse(row_data.first.first)
return false if data['dark_theme'].nil? || data['diffing'].nil? || data['register_speckle_entity'].nil?
begin
data = JSON.parse(row_data.first.first)
if data['dark_theme'].nil? || data['fe2'].nil? || data['diffing'].nil? || data['register_speckle_entity'].nil?
return false
end
true
true
rescue StandardError
false
end
end
# Validates current preferences. If there are incomplete data then this method resets it with default preferences.
@@ -65,11 +71,13 @@ module SpeckleConnector
data_hash = JSON.parse(row_data).to_h
# Get current theme value
dark_theme = data_hash['dark_theme']
fe2 = data_hash['fe2']
diffing = data_hash['diffing']
register_speckle_entity = data_hash['register_speckle_entity']
{
dark_theme: dark_theme,
fe2: fe2,
diffing: diffing,
register_speckle_entity: register_speckle_entity
}.freeze
@@ -0,0 +1,89 @@
# frozen_string_literal: true
require 'delegate'
require_relative 'dictionary_handler'
require_relative '../../constants/dict_constants'
module SpeckleConnector
module SketchupModel
module Dictionary
# Read and write attributes for Speckle objects on SketchUp model.
class BaseDictionaryHandler < DictionaryHandler
IGNORED_DICTIONARY_NAMES = [
SPECKLE_BASE_OBJECT,
'IFC 4',
'IFC 2x3'
].freeze
# @param entity [Sketchup::Entity] entity to get attribute dictionaries
def self.attribute_dictionaries_to_speckle(entity, model_preferences)
dictionaries = {}
return dictionaries unless model_preferences[INCLUDE_ENTITY_ATTRIBUTES]
klass = get_entity_setting_type(entity)
return dictionaries unless model_preferences[ENTITY_KEYS_FOR_INCLUDING_ATTRIBUTES[klass]]
return dictionaries if entity.attribute_dictionaries.nil?
entity.attribute_dictionaries.each do |att_dict|
dict_name = att_dict == '' ? 'empty_dictionary_name' : att_dict.name
dictionaries[dict_name] = att_dict.to_h unless IGNORED_DICTIONARY_NAMES.include?(att_dict.name)
end
dictionaries
end
# @param entity [Sketchup::Entity] entity to set attribute dictionaries
# rubocop:disable Metrics/CyclomaticComplexity
def self.attribute_dictionaries_to_native(entity, dictionaries)
return if dictionaries.nil?
classification_to_native(entity, dictionaries) if entity.is_a?(Sketchup::ComponentDefinition)
dictionaries.each do |dict_name, entries|
next unless entries.is_a?(Hash)
dict_name = dict_name == 'empty_dictionary_name' ? '' : dict_name
entries.each do |key, value|
set_attribute(entity, key, value, dict_name)
rescue StandardError => e
puts("Failed to write key: #{key} value: #{value} to dictionary #{dict_name}")
puts(e)
end
end
end
# rubocop:enable Metrics/CyclomaticComplexity
# Classification is ComponentDefinition specific, so they can be added only definition by add_classification
# method.
# @param definition_entity [Sketchup::ComponentDefinition] definition to add callback
def self.classification_to_native(definition_entity, dictionaries)
applied_schema_types = dictionaries['AppliedSchemaTypes']
return if applied_schema_types.nil?
applied_schema_types.each do |key, value|
definition_entity.add_classification(key, value)
end
end
# @return [String] the name of the dictionary to read from
def self.dictionary_name
SPECKLE_BASE_OBJECT
end
# Gets entity type for including entity attributes setting.
# @param entity [Sketchup::Entity] entity to find setting entity.
# @return [Sketchup::Face, Sketchup::Edge, Sketchup::Group, Sketchup::ComponentInstance]
def self.get_entity_setting_type(entity)
klass = entity.class
if entity.is_a?(Sketchup::ComponentDefinition)
klass = if entity.group?
Sketchup::Group
else
Sketchup::ComponentInstance
end
end
klass
end
end
end
end
end
@@ -1,65 +1,13 @@
# frozen_string_literal: true
require 'delegate'
require_relative '../../constants/dict_constants'
module SpeckleConnector
module SketchupModel
module Dictionary
# Read and write attributes from the groups and other entities that represents Speckle objects on SketchUp model.
class DictionaryHandler
DICTIONARY_NAME = 'Speckle_Base_Object'
IGNORED_DICTIONARY_NAMES = [
DICTIONARY_NAME,
'IFC 4',
'IFC 2x3'
].freeze
# @param entity [Sketchup::Entity] entity to get attribute dictionaries
def self.attribute_dictionaries_to_speckle(entity)
dictionaries = {}
return dictionaries if entity.attribute_dictionaries.nil?
entity.attribute_dictionaries.each do |att_dict|
dict_name = att_dict == '' ? 'empty_dictionary_name' : att_dict.name
dictionaries[dict_name] = att_dict.to_h unless IGNORED_DICTIONARY_NAMES.include?(att_dict.name)
end
dictionaries
end
# @param entity [Sketchup::Entity] entity to set attribute dictionaries
# rubocop:disable Metrics/CyclomaticComplexity
def self.attribute_dictionaries_to_native(entity, dictionaries)
return if dictionaries.nil?
classification_to_native(entity, dictionaries) if entity.is_a?(Sketchup::ComponentDefinition)
dictionaries.each do |dict_name, entries|
next unless entries.is_a?(Hash)
dict_name = dict_name == 'empty_dictionary_name' ? '' : dict_name
entries.each do |key, value|
set_attribute(entity, key, value, dict_name)
rescue StandardError => e
puts("Failed to write key: #{key} value: #{value} to dictionary #{dict_name}")
puts(e)
end
end
end
# rubocop:enable Metrics/CyclomaticComplexity
# Classification is ComponentDefinition specific, so they can be added only definition by add_classification
# method.
# @param definition_entity [Sketchup::ComponentDefinition] definition to add callback
def self.classification_to_native(definition_entity, dictionaries)
applied_schema_types = dictionaries['AppliedSchemaTypes']
return if applied_schema_types.nil?
applied_schema_types.each do |key, value|
definition_entity.add_classification(key, value)
end
end
# @param entity [Sketchup::Entity] the sketchup entity of Speckle object
# @param key [Symbol] the name of the attribute
# @param dictionary_name [String, Symbol] the name of the attribute dictionary
@@ -105,9 +53,15 @@ module SpeckleConnector
dictionary.delete_key(key)
end
# @param entity [Sketchup::Entity] the sketchup entity of Speckle object
# @param dictionary_name [String, Symbol] the name of the attribute dictionary to remove
def self.remove_dictionary(entity, dictionary_name = self.dictionary_name)
entity.attribute_dictionaries.delete(dictionary_name)
end
# @return [String] the name of the dictionary to read from
def self.dictionary_name
DICTIONARY_NAME
raise NotImplementedError 'Implement this in subclass'
end
end
end
@@ -1,6 +1,6 @@
# frozen_string_literal: true
require_relative 'dictionary_handler'
require_relative 'base_dictionary_handler'
require_relative '../../constants/dict_constants'
require_relative '../../constants/type_constants'
@@ -9,6 +9,8 @@ module SpeckleConnector
module Dictionary
# Dictionary handler of the speckle entity.
class SpeckleEntityDictionaryHandler < DictionaryHandler
DICTIONARY_NAME = SPECKLE_BASE_OBJECT
# Writes initial data while speckle entity is creating first time.
# @param sketchup_entity [Sketchup::Entity] Sketchup entity to write data into it's attribute dictionary.
# rubocop:disable Metrics/ParameterLists
@@ -26,6 +28,11 @@ module SpeckleConnector
set_hash(sketchup_entity, initial_dict_data)
end
# rubocop:enable Metrics/ParameterLists
# @return [String] the name of the dictionary to read from
def self.dictionary_name
DICTIONARY_NAME
end
end
end
end
@@ -1,6 +1,6 @@
# frozen_string_literal: true
require_relative 'dictionary_handler'
require_relative 'base_dictionary_handler'
require_relative '../../constants/dict_constants'
require_relative '../../constants/type_constants'
@@ -8,7 +8,7 @@ module SpeckleConnector
module SketchupModel
module Dictionary
# Dictionary handler of the speckle model.
class SpeckleModelDictionaryHandler < DictionaryHandler
class SpeckleModelDictionaryHandler < BaseDictionaryHandler
DICTIONARY_NAME = 'Speckle'
# Writes initial data while speckle entity is creating first time.
# @param sketchup_model [Sketchup::Model] Sketchup model to write data into it's attribute dictionary.
@@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'delegate'
require_relative 'dictionary_handler'
require_relative '../../constants/dict_constants'
module SpeckleConnector
module SketchupModel
module Dictionary
# Read and write attributes for Speckle objects' schema on SketchUp model.
class SpeckleSchemaDictionaryHandler < DictionaryHandler
def self.speckle_schema_to_speckle(entity)
schema = {}
return schema if entity.attribute_dictionaries.nil?
schema_dict = entity.attribute_dictionaries.find { |dict| dict.name == dictionary_name }
return schema if schema_dict.nil?
schema_dict.to_h
end
# @return [String] the name of the dictionary to read from
def self.dictionary_name
SPECKLE_MAPPING_TOOL_SCHEMA
end
end
end
end
end
@@ -0,0 +1,113 @@
# frozen_string_literal: true
module SpeckleConnector
module SketchupModel
# Query operations in sketchup model.
module Query
# Queries for entity.
class Entity
class << self
# Creates flat list for entities that defined in classes property. It searches from top to bottom to collect
# entities.
# @param entities_to_flat [Sketchup::Entities] entities to flat their children, grandchildren and so on..
# @param classes [Array<Class>] objects types to collect as flat list.
def flat_entities(entities_to_flat,
classes = [Sketchup::Edge, Sketchup::Face, Sketchup::ComponentInstance,
Sketchup::Group, Sketchup::ComponentDefinition])
entities = []
entities_to_flat.each do |entity|
entities.append(entity) if classes.include?(entity.class)
if entity.is_a?(Sketchup::Group) || entity.is_a?(Sketchup::ComponentInstance)
entities.append(entity.definition) if classes.include?(Sketchup::ComponentDefinition)
entities += flat_entities(entity.definition.entities, classes)
end
end
entities
end
# Create array for each entity with their path.
# @param entities_to_flat [Sketchup::Entities, Array<Sketchup::Entity>] entities to flat with their path.
# @param classes [Array<Class>] classes to flat. Put class into this array if you want to find their paths.
# @param path [Array<Object>] path for entity that we are in.
# @return [Array<Object>] entity with it's path as flat array. See example.
# @example
# path[0] is entity itself
# path[1..-1] rest as path from top to bottom
def flat_entities_with_path(entities_to_flat,
classes = [Sketchup::Edge, Sketchup::Face, Sketchup::ComponentInstance,
Sketchup::Group, Sketchup::ComponentDefinition],
path = [])
entities = []
entities_to_flat.each do |entity|
# Collect object itself
entities.append([entity] + path) if classes.include?(entity.class)
# entities[entity] = path if classes.include?(entity.class) && entities[entity].nil?
# Skip unless entity is a container entity like group or component.
next unless entity.is_a?(Sketchup::Group) || entity.is_a?(Sketchup::ComponentInstance)
# Add entity definition also with it's path.
entities[entity.definition] = path if classes.include?(Sketchup::ComponentDefinition)
# Collect sub-objects if object is a container at the same time.
sub_entities = flat_entities_with_path(entity.definition.entities.to_a,
classes, path + [entity])
entities += sub_entities
end
entities
end
# Calculates global transformation of entity by multiplying path entries from bottom to top by reversing path.
# @param entity [Sketchup::Entity] entity to find global transformation.
# @param path [Array<Object>] path that parents of entity that has transformation value to calculate global
# transformation of the entity.
# @return [Geom::Transformation] global transformation of the entity.
def global_transformation(entity, path)
# If entity is face, use Identity
global = entity.respond_to?(:transformation) ? entity.transformation : Geom::Transformation.new
path.reverse.each do |local|
global = local.transformation * global if local.respond_to?(:transformation)
end
global
end
# Global transformation search for entity that lies on only one instance.
# @param entity [Sketchup::Entity] entity to find global transformation.
def global_transformation_from_bottom(entity)
# If entity is face, use Identity
transformation = entity.respond_to?(:transformation) ? entity.transformation : Geom::Transformation.new
parent = parent_or_model(entity)
until parent.is_a?(Sketchup::Model) || parent.nil?
transformation = parent.transformation * transformation
parent = parent_or_model(parent)
end
transformation
end
# Parent search for entity from bottom to top. It is not ideal if entity lives in different instances.
def parent_or_model(entity)
parent = entity.parent
return parent if parent.is_a?(Sketchup::Model)
instances = parent.instances
if instances.length > 1
puts 'Parent has more than one instance'
instances.each(&:make_unique)
instances = instances.select { |instance| instance.definition.entities.include?(entity) }
end
instances.first
end
# Finds first material of parents from bottom to top.
def parent_material(path)
material = nil
path.reverse.each do |local|
material = local.material if local.respond_to?(:material)
return material unless material.nil?
end
material
end
end
end
end
end
end
@@ -0,0 +1,44 @@
# frozen_string_literal: true
module SpeckleConnector
module SketchupModel
# Query operations in sketchup model.
module Query
# Queries for layer and it's parents.
class Layer
class << self
# @param layer [Sketchup::Layer] layer to get folder path of the layer
# @return [Array<Sketchup::Folder>] path of the layer
def path(layer)
parent_folders = []
folder = layer.folder
until folder.nil?
parent_folders.append(folder)
folder = folder.folder
end
parent_folders.reverse
end
# @param entity [Sketchup::Entity] entity to find path.
def entity_path(entity, separation = '::')
path = path(entity.layer)
full_path = path.append(entity.layer)
full_path_string = ''
full_path.each_with_index do |layer, i|
full_path_string += layer.display_name
full_path_string += separation unless i == full_path.length - 1
end
full_path_string
end
# @param string_layer_path [String] string layer path to split.
def entity_layer_from_path(string_layer_path, separation = '::')
return string_layer_path if string_layer_path.nil?
string_layer_path.split(separation).last
end
end
end
end
end
end
@@ -0,0 +1,95 @@
# frozen_string_literal: true
require_relative '../dictionary/speckle_schema_dictionary_handler'
require_relative '../../speckle_entities/speckle_entity'
require_relative '../../mapper/category/revit_category'
require_relative '../../constants/dict_constants'
module SpeckleConnector
# Operations related to {SketchupModel}.
module SketchupModel
# Reader model for sketchup model.
module Reader
# Reader module for mapper.
module MapperReader
# @param entities [Sketchup::Entities] entities to collect mapped entities.
# @return [Hash{String=>Sketchup::Entity}] mapped entities with persistent id.
def self.read_mapped_entities(entities)
mapped_entities = {}
Query::Entity.flat_entities(entities).each do |entity|
mapped_entities[entity.persistent_id] = entity if mapped_with_schema?(entity)
end
mapped_entities
end
# @param entity [Sketchup::Entity] sketchup entity to check whether mapped with speckle schema or not.
def self.mapped_with_schema?(entity)
is_entity_mapped = !Dictionary::SpeckleSchemaDictionaryHandler.attribute_dictionary(entity).nil?
return is_entity_mapped if is_entity_mapped
return is_entity_mapped unless entity.is_a?(Sketchup::ComponentInstance)
!Dictionary::SpeckleSchemaDictionaryHandler.attribute_dictionary(entity.definition).nil?
end
def self.get_schema(entity)
Dictionary::SpeckleSchemaDictionaryHandler.speckle_schema_to_speckle(entity)
end
def self.entities_schema_details(entities)
entities.collect do |entity|
entity_selection_details = entity_selection_details(entity)
if entity.is_a?(Sketchup::ComponentInstance)
entity_selection_details = entity_selection_details.merge(
{ definition: entity_selection_details(entity.definition) }
)
end
entity_selection_details
end
end
def self.entity_selection_details(entity)
sanitized_type = entity.class.name.split('::').last.gsub(/(?<=[a-z])(?=[A-Z])/, ' ').split
is_definition = entity.is_a?(Sketchup::ComponentDefinition)
entity_type = is_definition ? sanitized_type.last : sanitized_type.first
speckle_schema = get_schema(entity)
{
name: speckle_schema['name'],
entityName: entity.respond_to?(:name) ? entity.name : '',
entityId: entity.persistent_id,
entityType: entity_type,
schema: speckle_schema,
numberOfInstances: is_definition ? entity.instances.length : 1
}
end
def self.mapped_entity_details(entities)
reverse_category_dictionary = Mapper::Category::RevitCategory.reverse_dictionary
entities.collect do |entity|
speckle_schema = get_schema(entity)
speckle_schema_definition = entity.respond_to?(:definition) ? get_schema(entity.definition) : nil
entity_type = entity.class.name.split('::').last.gsub(/(?<=[a-z])(?=[A-Z])/, ' ').split.first
category = get_map_attribute(speckle_schema, speckle_schema_definition, 'category')
{
name: get_map_attribute(speckle_schema, speckle_schema_definition, 'name'),
category: category,
categoryName: category.nil? ? '' : reverse_category_dictionary[category],
method: get_map_attribute(speckle_schema, speckle_schema_definition, 'method'),
entityName: entity.respond_to?(:name) ? entity.name : '',
entityId: entity.persistent_id,
entityType: entity.is_a?(Sketchup::ComponentDefinition) ? 'Definition' : entity_type,
schema: speckle_schema,
definitionSchema: speckle_schema_definition
}
end
end
def self.get_map_attribute(schema, definition_schema, attribute)
return schema[attribute] if schema[attribute]
return definition_schema[attribute] if !definition_schema.nil? && definition_schema[attribute]
nil
end
end
end
end
end
@@ -1,6 +1,8 @@
# frozen_string_literal: true
require_relative '../dictionary/speckle_schema_dictionary_handler'
require_relative '../../speckle_entities/speckle_entity'
require_relative '../../mapper/category/revit_category'
require_relative '../../constants/dict_constants'
module SpeckleConnector
@@ -11,6 +13,7 @@ module SpeckleConnector
# Reader module for speckle entities.
module SpeckleEntitiesReader
# @param entities [Sketchup::Entities] entities to collect speckle entities.
# @return [Hash{String=>Sketchup::Entity}] speckle entities with persistent id.
def self.read(entities)
speckle_entities = {}
entities.each do |entity|
@@ -46,6 +49,8 @@ module SpeckleConnector
entity.attribute_dictionaries.to_a.any? { |dict| dict.name == SPECKLE_BASE_OBJECT }
end
end
end
end
@@ -1,5 +1,7 @@
# frozen_string_literal: true
require_relative '../../constants/geo_constants'
module SpeckleConnector
# Operations related to {SketchupModel}.
module SketchupModel
@@ -24,6 +26,13 @@ module SpeckleConnector
end
adj_faces.uniq
end
# @param face [Sketchup::Face] face to get max z distance for all vertices.
def self.max_z(face)
points = face.vertices.collect(&:position)
points_z_values = points.collect(&:z)
points_z_values.max - points_z_values.min
end
end
end
end
@@ -0,0 +1,84 @@
# frozen_string_literal: true
module SpeckleConnector
# Operations related to {SketchupModel}.
module SketchupModel
# Works directly with/on SketchUp Entities of different kinds (Groups, Faces, Edges, ...).
module Utils
# Static methods to do plane calculations with sketchup geom objects like Point3d and Vector3d.
class Plane
LENGTH_TOLERANCE = 1e-8
# Create plane from 3 points
# @param origin [Geom::Point3d] the point on the plane that wil become the origin of the local coordinate system
# @param point_1 [Geom::Point3d] the point that defines first direction
# @param point_2 [Geom::Point3d] the third point on the plane
# @return [Plane] the parametrization of the plane that goes through the given points
def self.from_points(origin, point_1, point_2)
direction_x = origin.vector_to(point_1).normalize
direction_x = direction_x.normalize
normal = direction_x.cross(origin.vector_to(point_2))
direction_y = direction_x.cross(normal.normalize)
new(origin: origin, direction_u: direction_x, direction_v: direction_y)
end
# @return [Geom::Vector3d] the direction of the u-axis on the plane
attr_reader :direction_u
# @return [Geom::Vector3d] the direction of the v-axis on the plane
attr_reader :direction_v
# @return [Geom::Point3d] the origin of the local coordinate system on the plane
attr_reader :origin
# @param origin [Geom::Point3d] the origin of the coordinate system on the plane
# @param direction_u [Geom::Vector3d] the direction of the x-axis
# @param direction_v [Geom::Vector3d] the direction of the y-axis
def initialize(origin:, direction_u:, direction_v:)
@origin = origin
@direction_u = direction_u
@direction_v = direction_v
end
# Get the point object in global coordinates for the point on the plane with local coordinates (u,v).
# @param coordinate_u [Float] the u-coordinate on the plane
# @param coordinate_v [Float] the v-coordinate on the plane
# @return [Geom::Point3d] the point in space that corresponds to the given (u, v) coordinates
def point_at(coordinate_u, coordinate_v)
scaled_direction_u = Geom::Vector3d.new(direction_u.x * coordinate_u,
direction_u.y * coordinate_u,
direction_u.z * coordinate_u)
scaled_direction_v = Geom::Vector3d.new(direction_v.x * coordinate_v,
direction_v.y * coordinate_v,
direction_v.z * coordinate_v)
origin + scaled_direction_u + scaled_direction_v
end
# Find local (u, v) coordinates of the projection of the given point to the plane
# @param point [Geom::Point3d] the point that will be projected to the plane
# @return [(Float, Float)] the local coordinates on the plane that correspond to the projected point
def plane_coordinates(point)
origin_to_point = origin.vector_to(point)
coordinate_u = origin_to_point.dot(direction_u)
coordinate_v = origin_to_point.dot(direction_v)
[coordinate_u, coordinate_v]
end
# Project a given point to the plane
# @param point [Geom::Point3d] the point that will be projected to the plane
# @return [Geom::Point3d] the projected point on the plane
def project_to_plane(point)
coordinate_u, coordinate_v = plane_coordinates(point)
point_at(coordinate_u, coordinate_v)
end
# Check if the given point lies on the plane
# @param point [Geom::Point3d] the point to check
# @return [Boolean] whether the point lies on the plane
def on_plane?(point)
point.distance(project_to_plane(point)).to_m < LENGTH_TOLERANCE
end
end
end
end
end
@@ -51,9 +51,9 @@ module SpeckleConnector
@sketchup_entity = sketchup_entity
@application_id = application_id
@id = speckle_id
@total_children_count = children.length
@total_children_count = children.nil? ? 0 : children.length
@speckle_type = speckle_type
@speckle_children_objects = children
@speckle_children_objects = children.nil? ? [] : children
end
# rubocop:enable Metrics/ParameterLists
@@ -119,6 +119,41 @@ module SpeckleConnector
def valid?
sketchup_entity.valid?
end
# @param state [States::State] state of the application
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/CyclomaticComplexity
def self.from_speckle_object(state, speckle_object, entities, stream_id)
return state, [] if entities.empty?
speckle_id = speckle_object['id']
application_id = speckle_object['applicationId']
speckle_type = speckle_object['speckle_type']
children = speckle_object['__closure'].nil? ? [] : speckle_object['__closure']
speckle_state = state.speckle_state
entities.each do |entity|
next if entity.is_a?(Sketchup::Material) || entity.is_a?(Sketchup::Page)
next if (entity.is_a?(Sketchup::Face) || entity.is_a?(Sketchup::Edge)) &&
!state.user_state.user_preferences[:register_speckle_entity]
if entity.is_a?(Sketchup::ComponentDefinition)
definition = speckle_object['definition'] || speckle_object['@block_definition'] ||
speckle_object['block_definition']
if definition
speckle_id = definition['id']
speckle_type = definition['speckle_type']
end
end
ent = SpeckleEntity.new(entity, speckle_id, application_id, speckle_type, children, [stream_id])
ent.write_initial_base_data
speckle_state = speckle_state.with_speckle_entity(ent)
end
new_state = state.with_speckle_state(speckle_state)
return new_state, entities
end
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
end
end
end
@@ -0,0 +1,53 @@
# frozen_string_literal: true
require_relative '../base'
require_relative '../built_elements/revit/parameter'
require_relative '../other/render_material'
require_relative '../geometry/line'
require_relative '../geometry/polyline'
require_relative '../../constants/type_constants'
require_relative '../../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module SpeckleObjects
module BuiltElements
# Default Floor object.
class DefaultFloor < Base
SPECKLE_TYPE = OBJECTS_BUILTELEMENTS_DEFAULT_FLOOR
def initialize(outline:, voids:, units:, material:, application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: application_id,
id: nil
)
self[:outline] = outline
self[:voids] = voids
self[:units] = units
self[:renderMaterial] = material
end
# @param face [Sketchup::Face] face to get speckle schema for floor.
def self.to_speckle_schema(face, units, global_transformation: nil)
outline = Geometry::Polyline.from_loop(face.loops.first, units, global_transformation: global_transformation)
voids = []
if face.loops.length > 1
voids = face.loops[1..face.loops.length - 1].collect do |loop|
Geometry::Polyline.from_loop(loop, units, global_transformation: global_transformation)
end
end
material = face.material || face.back_material
DefaultFloor.new(
outline: outline,
voids: voids,
units: units,
material: material.nil? ? nil : Other::RenderMaterial.from_material(face.material || face.back_material),
application_id: face.persistent_id
)
end
end
end
end
end
@@ -0,0 +1,52 @@
# frozen_string_literal: true
require_relative '../base'
require_relative '../built_elements/revit/parameter'
require_relative '../other/render_material'
require_relative '../geometry/length'
require_relative '../geometry/line'
require_relative '../geometry/polyline'
require_relative '../../constants/type_constants'
require_relative '../../sketchup_model/dictionary/speckle_schema_dictionary_handler'
require_relative '../../sketchup_model/utils/face_utils'
module SpeckleConnector
module SpeckleObjects
module BuiltElements
# Default Wall object.
class DefaultWall < Base
SPECKLE_TYPE = OBJECTS_BUILTELEMENTS_DEFAULT_WALL
def initialize(base_line:, height:, flipped:, units:, material:, application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: application_id,
id: nil
)
self[:baseLine] = base_line
self[:height] = height
self[:flipped] = flipped
self[:units] = units
self[:renderMaterial] = material
end
# @param face [Sketchup::Face] face to get speckle schema for floor.
def self.to_speckle_schema(face, units, global_transformation: nil)
base_line = Geometry::Line.base_line_from_face(face, units, global_transformation: global_transformation)
material = face.material || face.back_material
DefaultWall.new(
base_line: base_line,
height: Geometry.length_to_speckle(SketchupModel::Utils::FaceUtils.max_z(face), units),
flipped: false,
units: units,
material: material.nil? ? nil : Other::RenderMaterial.from_material(face.material || face.back_material),
application_id: face.persistent_id
)
end
end
end
end
end
@@ -0,0 +1,84 @@
# frozen_string_literal: true
require_relative '../base'
require_relative '../other/render_material'
require_relative '../geometry/line'
require_relative '../geometry/length'
require_relative '../geometry/polyline'
require_relative '../../constants/type_constants'
require_relative '../../sketchup_model/dictionary/speckle_entity_dictionary_handler'
module SpeckleConnector
module SpeckleObjects
module BuiltElements
# Level object.
class Level < Base
SPECKLE_TYPE = OBJECTS_BUILTELEMENTS_REVIT_LEVEL
def initialize(name:, elevation:, units:, element_id:, application_id: nil, id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: application_id,
id: id
)
self[:name] = name
self[:elevation] = elevation
self[:units] = units
self[:elementId] = element_id
self[:referenceOnly] = true
self[:createView] = false
end
# @param state [States::State] state of the application.
def self.to_native(state, speckle_level, stream_id)
sketchup_model = state.sketchup_state.sketchup_model
levels_layer = sketchup_model.layers.layers.find { |layer| layer.display_name == 'Levels' }
levels_layer = sketchup_model.layers.add('Levels') if levels_layer.nil?
name = speckle_level['name']
elevation = speckle_level['elevation']
units = speckle_level['units']
element_id = speckle_level['elementId']
application_id = speckle_level['applicationId']
id = speckle_level['id']
skp_elevation = Geometry.length_to_native(elevation, units)
definition_name = "#{name}-#{application_id}"
definition = sketchup_model.definitions.find { |definition| definition.name == definition_name }
definition.entities.clear! unless definition.nil?
definition = sketchup_model.definitions.add(definition_name) if definition.nil?
instance = sketchup_model.entities.add_instance(definition, Geom::Transformation.new)
instance.locked = true
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.write_initial_base_data(
instance, application_id, id, SPECKLE_TYPE, [], stream_id
)
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.set_attribute(instance, :name, name)
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.write_initial_base_data(
definition, application_id, id, SPECKLE_TYPE, [], stream_id
)
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.set_attribute(definition, :name, name)
c1_e = Geom::Point3d.new(0, 10.m, skp_elevation)
c2_e = Geom::Point3d.new(0, 0, skp_elevation)
c3_e = Geom::Point3d.new(10.m, 0, skp_elevation)
cline_1 = definition.entities.add_cline(c1_e, c2_e)
cline_2 = definition.entities.add_cline(c2_e, c3_e)
text = definition.entities.add_text(" #{name}", c1_e)
[cline_1, cline_2, text, definition, instance].each { |o| o.layer = levels_layer }
Level.new(
name: name,
elevation: elevation,
units: units,
element_id: element_id,
application_id: application_id,
id: speckle_level['id']
)
end
end
end
end
end
@@ -0,0 +1,23 @@
# frozen_string_literal: true
require_relative '../base'
require_relative '../../constants/type_constants'
module SpeckleConnector
module SpeckleObjects
module BuiltElements
# Network object represents scenes on Sketchup.
class Network < Base
SPECKLE_TYPE = OBJECTS_BUILTELEMENTS_NETWORK
def self.to_native(state, network, layer, entities, &convert_to_native)
network['elements'].each do |element|
state, _converted_entities = convert_to_native.call(state, element['elements'], layer, entities)
end
return state, []
end
end
end
end
end
@@ -0,0 +1,145 @@
# frozen_string_literal: true
require_relative '../../base'
require_relative '../../other/render_material'
require_relative '../../other/block_instance'
require_relative '../../other/block_definition'
require_relative '../../other/transform'
require_relative '../../../constants/type_constants'
require_relative '../../../sketchup_model/query/entity'
require_relative '../../../sketchup_model/reader/mapper_reader'
require_relative '../../../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module SpeckleObjects
module BuiltElements
module Revit
# Direct shape definition for Revit mappings.
class DirectShape < Base
SPECKLE_TYPE = OBJECTS_BUILTELEMENTS_REVIT_DIRECTSHAPE
READER = SketchupModel::Reader
QUERY = SketchupModel::Query
DICTIONARY = SketchupModel::Dictionary
def initialize(name:, category:, units:, base_geometries:, application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: application_id,
id: nil
)
self[:name] = name
self[:category] = category
self[:units] = units
self[:baseGeometries] = base_geometries
end
def self.get_direct_shape_name(direct_shape)
if direct_shape['name'] == ''
direct_shape['applicationId'].to_s
else
"#{direct_shape['name']}::#{direct_shape['applicationId']}"
end
end
# @param state [States::State] state of the application.
def self.to_native(state, direct_shape, layer, entities, &convert_to_native)
direct_shape['geometry'] = direct_shape['baseGeometries']
direct_shape['name'] = get_direct_shape_name(direct_shape)
state, _definitions = Other::BlockDefinition.to_native(
state, direct_shape, layer, entities, &convert_to_native
)
definition = state.sketchup_state.sketchup_model
.definitions[Other::BlockDefinition.get_definition_name(direct_shape)]
instance = entities.add_instance(definition, Geom::Transformation.new)
instance.name = direct_shape['name'] unless direct_shape['name'].nil?
DICTIONARY::SpeckleSchemaDictionaryHandler.set_hash(
instance,
{
name: direct_shape['name'], category: direct_shape['category'], method: 'Direct Shape'
}
)
new_speckle_state = state.speckle_state.with_mapped_entity(instance)
state = state.with_speckle_state(new_speckle_state)
instance.layer = layer unless layer.nil?
return state, [instance, definition]
end
# Collects direct shapes on selection as flat list.
def self.direct_shapes_on_selection(sketchup_model)
flat_selection_with_path = QUERY::Entity.flat_entities_with_path(
sketchup_model.selection,
[Sketchup::Face, Sketchup::ComponentInstance, Sketchup::Group], [sketchup_model]
)
mapped_selection = []
flat_selection_with_path.each do |entities|
entity = entities[0]
is_entity_mapped = READER::MapperReader.mapped_with_schema?(entity)
if entity.respond_to?(:definition)
is_definition_mapped = READER::MapperReader.mapped_with_schema?(entity.definition)
mapped_selection.append(entities) if is_entity_mapped || is_definition_mapped
next
end
mapped_selection.append(entities) if is_entity_mapped
end
mapped_selection
end
def self.from_entity(speckle_state, entity, path, units, preferences)
schema = DICTIONARY::SpeckleSchemaDictionaryHandler.attribute_dictionary(entity)
if schema.nil? && entity.respond_to?(:definition)
schema = DICTIONARY::SpeckleSchemaDictionaryHandler.attribute_dictionary(entity.definition)
end
entities_with_path = []
entities_with_path.append([entity] + path) if entity.is_a?(Sketchup::Face) || entity.is_a?(Sketchup::Edge)
# Collect here flat list
if entity.is_a?(Sketchup::ComponentInstance) || entity.is_a?(Sketchup::Group)
entities_with_path += QUERY::Entity
.flat_entities_with_path(
entity.definition.entities, [Sketchup::Face], path.append(entity)
)
end
base_geometries = group_faces_under_mesh_by_material(speckle_state, entities_with_path, units, preferences)
DirectShape.new(
name: schema[:name], category: schema[:category], units: units,
base_geometries: base_geometries, application_id: entity.persistent_id
)
end
# rubocop:disable Metrics/MethodLength
def self.group_faces_under_mesh_by_material(speckle_state, faces_with_path, units, preferences)
mesh_groups = {}
faces_with_path.each do |face_with_path|
face = face_with_path[0]
entity_path = face_with_path[1..-1]
parent_material = QUERY::Entity.parent_material(entity_path)
mesh_group_id = Geometry::Mesh.get_mesh_group_id(face, preferences[:model], parent_material)
if mesh_groups.key?(mesh_group_id)
mesh_group = mesh_groups[mesh_group_id]
mesh_group[0].face_to_mesh(face, QUERY::Entity.global_transformation(face, entity_path))
mesh_group[1].append(face)
else
mesh = Geometry::Mesh.from_face(
speckle_state: speckle_state,
face: face, units: units, model_preferences: preferences[:model],
global_transform: QUERY::Entity.global_transformation(face, entity_path),
parent_material: parent_material
)
mesh_groups[mesh_group_id] = [mesh, [face]]
end
end
# Update mesh overwrites points and polygons into base object.
mesh_groups.each { |_, mesh| mesh.first.update_mesh }
mesh_groups.values
end
# rubocop:enable Metrics/MethodLength
end
end
end
end
end
@@ -0,0 +1,26 @@
# frozen_string_literal: true
require_relative '../../base'
require_relative '../../../constants/type_constants'
module SpeckleConnector
module SpeckleObjects
module BuiltElements
module Revit
# Revit parameter.
class Parameter < Base
SPECKLE_TYPE = OBJECTS_BUILTELEMENTS_REVIT_PARAMETER
def initialize(name:, application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: application_id,
id: id
)
self[:name] = name
end
end
end
end
end
end
@@ -0,0 +1,42 @@
# frozen_string_literal: true
require_relative '../../base'
module SpeckleConnector
module SpeckleObjects
module BuiltElements
module Revit
# Revit element type.
class RevitElementType < Base
SPECKLE_TYPE = OBJECTS_BUILTELEMENTS_REVIT_REVITELEMENTTYPE
# rubocop:disable Metrics/ParameterLists
def initialize(category:, family:, type:, element_id:, application_id: nil, id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: application_id,
id: id
)
self[:category] = category
self[:family] = family
self[:type] = type
self[:elementId] = element_id
end
# rubocop:enable Metrics/ParameterLists
def self.to_native(revit_element_type)
RevitElementType.new(
category: revit_element_type['category'],
family: revit_element_type['family'],
type: revit_element_type['type'],
element_id: revit_element_type['elementId'],
application_id: revit_element_type['applicationId'],
id: revit_element_type['id']
)
end
end
end
end
end
end
@@ -0,0 +1,77 @@
# frozen_string_literal: true
require_relative '../../base'
require_relative '../../built_elements/revit/parameter'
require_relative '../../other/render_material'
require_relative '../../geometry/line'
require_relative '../../geometry/polyline'
require_relative '../../../constants/type_constants'
require_relative '../../../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module SpeckleObjects
module BuiltElements
# Revit floor object.
class RevitFloor < Base
SPECKLE_TYPE = OBJECTS_BUILTELEMENTS_REVIT_FLOOR
# rubocop:disable Metrics/ParameterLists
def initialize(family:, type:, outline:, voids:, level:, units:, material:, parameters:nil, application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: application_id,
id: nil
)
self[:family] = family
self[:type] = type
self[:level] = level
self[:outline] = outline
self[:voids] = voids
self[:units] = units
self[:parameters] = parameters
self[:renderMaterial] = material
end
# rubocop:enable Metrics/ParameterLists
# @param face [Sketchup::Face] face to get speckle schema for floor.
def self.to_speckle_schema(speckle_state, face, units, global_transformation: nil)
outline = Geometry::Polyline.from_loop(face.loops.first, units, global_transformation: global_transformation)
voids = []
if face.loops.length > 1
voids = face.loops[1..face.loops.length - 1].collect do |loop|
Geometry::Polyline.from_loop(loop, units, global_transformation: global_transformation)
end
end
material = face.material || face.back_material
schema = SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.speckle_schema_to_speckle(face).to_h
source_exist = !speckle_state.speckle_mapper_state.mapper_source.nil?
level = nil
parameters = nil
if source_exist
level = speckle_state.speckle_mapper_state.mapper_source.levels.find { |l| l[:name] == schema['level'] }
parameters = Base.new
offset_parameter = BuiltElements::Revit::Parameter.new(name: 'Height Offset From Level')
level_z = Geometry.length_to_native(level[:elevation], level[:units])
min_z = face.vertices.collect(&:position).map(&:z).min
offset_parameter['value'] = Geometry.length_to_speckle(min_z - level_z, units)
offset_parameter['units'] = units
parameters['Height Offset From Level'] = offset_parameter
end
RevitFloor.new(
family: schema['family'],
type: schema['family_type'],
outline: outline,
voids: voids,
level: level,
units: units,
parameters: parameters,
material: material.nil? ? nil : Other::RenderMaterial.from_material(face.material || face.back_material),
application_id: face.persistent_id
)
end
end
end
end
end
@@ -0,0 +1,102 @@
# frozen_string_literal: true
require_relative '../../base'
require_relative '../../built_elements/revit/parameter'
require_relative '../../other/render_material'
require_relative '../../geometry/line'
require_relative '../../geometry/length'
require_relative '../../../constants/type_constants'
require_relative '../../../sketchup_model/dictionary/speckle_schema_dictionary_handler'
require_relative '../../../sketchup_model/utils/face_utils'
module SpeckleConnector
module SpeckleObjects
module BuiltElements
# Revit wall object.
class RevitWall < Base
SPECKLE_TYPE = OBJECTS_BUILTELEMENTS_REVIT_WALL
# rubocop:disable Metrics/ParameterLists
def initialize(family:, type:, base_line:, height:, flipped:, level:, units:, material:, parameters: nil, application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: application_id,
id: nil
)
self[:family] = family
self[:type] = type
self[:height] = height
self[:flipped] = flipped
self[:level] = level
self[:baseLine] = base_line
self[:units] = units
self[:parameters] = parameters
self[:renderMaterial] = material
end
# rubocop:enable Metrics/ParameterLists
def self.to_native(state, wall, layer, entities, &convert_to_native)
obj = Other::DisplayValue.collect_definition_geometries(wall)
obj['name'] = Other::DisplayValue.get_definition_name(obj)
state, _definitions = Other::BlockDefinition.to_native(
state, obj, layer, entities, &convert_to_native
)
definition = state.sketchup_state.sketchup_model.definitions[Other::BlockDefinition.get_definition_name(obj)]
Other::BlockInstance.find_and_erase_existing_instance(definition, obj['id'], obj['applicationId'])
t_arr = obj['transform']
transform = t_arr.nil? ? Geom::Transformation.new : Other::Transform.to_native(t_arr, obj['units'])
instance = entities.add_instance(definition, transform)
instance.name = Other::DisplayValue.get_instance_name(obj['name']) unless obj['name'].nil?
instance.layer = layer unless layer.nil?
# Align instance axes that created from display value. (without any transform)
# BlockInstance.align_instance_axes(instance)
return state, [instance, definition]
end
# @param face [Sketchup::Face] face to get speckle schema for wall.
def self.to_speckle_schema(speckle_state, face, units, global_transformation: nil)
base_line = Geometry::Line.base_line_from_face(face, units, global_transformation: global_transformation)
material = face.material || face.back_material
schema = SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.speckle_schema_to_speckle(face).to_h
source_exist = !speckle_state.speckle_mapper_state.mapper_source.nil?
level = nil
parameters = nil
if source_exist
level = speckle_state.speckle_mapper_state.mapper_source.levels.find { |l| l[:name] == schema['level'] }
parameters = Base.new
offset_parameter = BuiltElements::Revit::Parameter.new(name: 'Height Offset From Level')
level_z = Geometry.length_to_native(level[:elevation], level[:units])
min_z = face.vertices.collect(&:position).map(&:z).min
offset_parameter['value'] = Geometry.length_to_speckle(min_z - level_z, units)
offset_parameter['units'] = units
parameters['Height Offset From Level'] = offset_parameter
end
RevitWall.new(
family: schema['family'],
type: schema['family_type'],
base_line: base_line,
height: Geometry.length_to_speckle(SketchupModel::Utils::FaceUtils.max_z(face), units),
flipped: false,
level: level,
units: units,
parameters: parameters,
material: material.nil? ? nil : Other::RenderMaterial.from_material(face.material || face.back_material),
application_id: face.persistent_id
)
end
def self.get_wall_height(face, units)
points = face.vertices.collect(&:position)
points_z_values = points.collect(&:z)
Geometry.length_to_speckle(points_z_values.max - points_z_values.min, units)
end
end
end
end
end
@@ -2,6 +2,7 @@
require_relative '../base'
require_relative '../../constants/type_constants'
require_relative '../../speckle_objects/geometry/length'
require_relative '../../speckle_objects/geometry/point'
require_relative '../../speckle_objects/geometry/vector'
@@ -18,7 +19,7 @@ module SpeckleConnector
# @param direction [SpeckleObjects::Geometry::Vector] direction of the view from eye to target.
# @param up_direction [SpeckleObjects::Geometry::Vector] up direction of the view.
# @param is_perspective [Boolean] whether view is perspective or not.
# @param lens [Boolean] fov value of the view camera.
# @param lens [Numeric] focal length value of the view camera.
# @param units [String] units of the camera.
# @param application_id [String] application_id of the view.
# @param update_properties [Hash{Symbol=>boolean}] properties of the view.
@@ -45,48 +46,123 @@ module SpeckleConnector
end
# rubocop:enable Metrics/ParameterLists
# Collects scenes as views from sketchup model.
# @param sketchup_model [Sketchup::Model] sketchup model to collect views from pages.
# @param units [String] units of the model.
def self.from_model(sketchup_model, units)
sketchup_model.pages.collect { |page| from_page(page, units) }
end
# @param page [Sketchup::Page] page to convert speckle view.
def self.from_page(page, units)
cam = page.camera
origin = get_camera_origin(cam, units)
target = get_camera_target(cam, units)
direction = get_camera_direction(cam, units)
update_properties = get_scene_update_properties(page)
rendering_options = SpeckleObjects::Other::RenderingOptions.to_speckle(page.rendering_options)
View3d.new(
page.name, origin, target, direction, SpeckleObjects::Geometry::Vector.new(0, 0, 1, units),
cam.perspective?, cam.perspective? ? cam.focal_length : 35, units, page.name,
update_properties, rendering_options
)
end
# @param state [States::State] state of the speckle app.
# @param obj [Hash] commit object.
# @param sketchup_model [Sketchup::Model] active sketchup model.
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/CyclomaticComplexity
def self.to_native(obj, sketchup_model)
views = collect_views(obj)
return if views.empty?
def self.to_native(state, view, _layer, _entities, &_convert_to_native)
sketchup_model = state.sketchup_state.sketchup_model
return state, [] unless view['speckle_type'] == 'Objects.BuiltElements.View:Objects.BuiltElements.View3D'
views.each do |view|
next unless view['speckle_type'] == 'Objects.BuiltElements.View:Objects.BuiltElements.View3D'
name = view['name'] || view['id']
return state, [] if sketchup_model.pages.any? { |page| page.name == name }
name = view['name'] || view['id']
next if sketchup_model.pages.any? { |page| page.name == name }
camera = create_camera(view)
sketchup_model.active_view.camera = camera
sketchup_model.pages.add(name)
page = sketchup_model.pages[name]
set_page_update_properties(page, view['update_properties']) if view['update_properties']
set_rendering_options(page.rendering_options, view['rendering_options']) if view['rendering_options']
return state, [page]
end
origin = view['origin']
target = view['target']
lens = view['lens'] || 50
origin = SpeckleObjects::Geometry::Point.to_native(origin['x'], origin['y'], origin['z'], origin['units'])
target = SpeckleObjects::Geometry::Point.to_native(target['x'], target['y'], target['z'], target['units'])
# Set camera position before creating scene on it.
my_camera = Sketchup::Camera.new(origin, target, [0, 0, 1], !view['isOrthogonal'], lens)
sketchup_model.active_view.camera = my_camera
sketchup_model.pages.add(name)
page = sketchup_model.pages[name]
set_page_update_properties(page, view['update_properties']) if view['update_properties']
set_rendering_options(page.rendering_options, view['rendering_options']) if view['rendering_options']
def self.create_camera(view)
origin = view['origin']
target = view['target']
focal_length = view['lens'] || 35
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'])
view_direction = (origin - target).normalize
up = view_direction.parallel?([0, 0, 1]) ? [0, 1, 0] : [0, 0, 1]
# Set camera position before creating scene on it.
is_perspective = !view['isOrthogonal']
camera = Sketchup::Camera.new(origin, target, up, is_perspective)
camera.focal_length = focal_length if is_perspective
camera.height = (origin - target).length * 2 unless is_perspective
camera
end
# @param page [Sketchup::Page] scene to update -update properties-
def self.set_page_update_properties(page, update_properties)
update_properties.each do |prop, value|
page.instance_variable_set(:"@#{prop}", value)
end
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
def self.collect_views(obj)
views = []
views += obj.filter_map do |_key, value|
if value.is_a?(Array) &&
value.any? { |v| v['speckle_type'] == OBJECTS_BUILTELEMENTS_VIEW3D }
value
end
# @param rendering_options [Sketchup::RenderingOptions] rendering options of scene (page)
def self.set_rendering_options(rendering_options, speckle_rendering_options)
speckle_rendering_options.each do |prop, value|
next if rendering_options[prop].nil?
rendering_options[prop] = if value.is_a?(Hash)
SpeckleObjects::Other::Color.to_native(value)
else
value
end
end
views.flatten.select { |view| view['speckle_type'] == OBJECTS_BUILTELEMENTS_VIEW3D }
end
# Get scene properties
# @param page [Sketchup::Page] page on sketchup.
def self.get_scene_update_properties(page)
{
use_axes: page.use_axes?,
use_camera: page.use_camera?,
use_hidden_geometry: page.use_hidden_geometry?,
use_hidden_layers: page.use_hidden_layers?,
use_hidden_objects: page.use_hidden_objects?,
use_rendering_options: page.use_rendering_options?,
use_section_planes: page.use_section_planes?,
use_shadow_info: page.use_shadow_info?,
use_style: page.use_style?
}
end
def self.get_camera_direction(camera, units)
SpeckleObjects::Geometry::Vector.new(
SpeckleObjects::Geometry.length_to_speckle(camera.direction[0], units),
SpeckleObjects::Geometry.length_to_speckle(camera.direction[1], units),
SpeckleObjects::Geometry.length_to_speckle(camera.direction[2], units),
units
)
end
def self.get_camera_target(camera, units)
SpeckleObjects::Geometry::Point.new(
SpeckleObjects::Geometry.length_to_speckle(camera.target[0], units),
SpeckleObjects::Geometry.length_to_speckle(camera.target[1], units),
SpeckleObjects::Geometry.length_to_speckle(camera.target[2], units),
units
)
end
def self.get_camera_origin(camera, units)
SpeckleObjects::Geometry::Point.new(
SpeckleObjects::Geometry.length_to_speckle(camera.eye[0], units),
SpeckleObjects::Geometry.length_to_speckle(camera.eye[1], units),
SpeckleObjects::Geometry.length_to_speckle(camera.eye[2], units),
units
)
end
end
end
@@ -0,0 +1,31 @@
# frozen_string_literal: true
require_relative '../base'
require_relative '../../constants/type_constants'
module SpeckleConnector
module SpeckleObjects
module Geometry
# Arc object definition for Speckle.
class Arc < Base
SPECKLE_TYPE = OBJECTS_GEOMETRY_ARC
# @param [States::State] state of the current application.
# @param arc [Object] object represents Speckle Arc.
# @param layer [Sketchup::Layer] layer to add {Sketchup::Edge} into it.
# @param entities [Sketchup::Entities] entities collection to add {Sketchup::Edge} into it.
def self.to_native(state, arc, layer, entities, &_convert_to_native)
plane = arc['plane']
units = arc['units']
origin = Point.to_native(plane['origin']['x'], plane['origin']['y'], plane['origin']['z'], units)
normal = Vector.to_native(plane['normal']['x'], plane['normal']['y'], plane['normal']['z'], units)
x_axis = Vector.to_native(plane['xdir']['x'], plane['xdir']['y'], plane['xdir']['z'], units)
radius = Geometry.length_to_native(arc['radius'], units)
edges = entities.add_arc(origin, x_axis, normal, radius, arc['startAngle'], arc['endAngle'])
edges.each { |edge| edge.layer = layer }
return state, edges
end
end
end
end
end
@@ -0,0 +1,33 @@
# frozen_string_literal: true
require_relative 'point'
require_relative 'vector'
require_relative 'length'
require_relative '../base'
require_relative '../../constants/type_constants'
module SpeckleConnector
module SpeckleObjects
module Geometry
# Circle object definition for Speckle.
class Circle < Base
SPECKLE_TYPE = OBJECTS_GEOMETRY_CIRCLE
# @param [States::State] state of the current application.
# @param circle [Object] object represents Speckle Circle.
# @param layer [Sketchup::Layer] layer to add {Sketchup::Edge} into it.
# @param entities [Sketchup::Entities] entities collection to add {Sketchup::Edge} into it.
def self.to_native(state, circle, layer, entities, &_convert_to_native)
plane = circle['plane']
units = circle['units']
origin = Point.to_native(plane['origin']['x'], plane['origin']['y'], plane['origin']['z'], units)
normal = Vector.to_native(plane['normal']['x'], plane['normal']['y'], plane['normal']['z'], units)
radius = Geometry.length_to_native(circle['radius'], units)
edges = entities.add_circle(origin, normal, radius)
edges.each { |edge| edge.layer = layer }
return state, edges
end
end
end
end
end
@@ -5,7 +5,8 @@ require_relative 'point'
require_relative 'bounding_box'
require_relative '../base'
require_relative '../primitive/interval'
require_relative '../../sketchup_model/dictionary/dictionary_handler'
require_relative '../../sketchup_model/dictionary/base_dictionary_handler'
require_relative '../../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module SpeckleObjects
@@ -17,11 +18,11 @@ module SpeckleConnector
# @param start_pt [Geometry::Point] start point speckle object of the speckle line.
# @param end_pt [Geometry::Point] end point speckle object of the speckle line.
# @param domain [Primitive::Interval] interval speckle object of the speckle line -represents domain.
# @param bbox [Geometry::BoundingBox] bounding box speckle object of the speckle line.
# @param units [String] units of the speckle line.
# @param application_id [String, nil] entity id of the {Sketchup::Edge} that represents to the speckle line.
# rubocop:disable Metrics/ParameterLists
def initialize(start_pt:, end_pt:, domain:, bbox:, units:, sketchup_attributes: {}, application_id: nil)
def initialize(start_pt:, end_pt:, domain:, units:, layer:,
sketchup_attributes: {}, speckle_schema: {}, application_id: nil)
super(
speckle_type: 'Objects.Geometry.Line',
total_children_count: 0,
@@ -31,39 +32,76 @@ module SpeckleConnector
self[:start] = start_pt
self[:end] = end_pt
self[:domain] = domain
self[:bbox] = bbox
self[:units] = units
self[:layer] = layer unless layer.nil?
self[:SpeckleSchema] = speckle_schema if speckle_schema.any?
self[:sketchup_attributes] = sketchup_attributes if sketchup_attributes.any?
end
# rubocop:enable Metrics/ParameterLists
# @param edge [Sketchup::Edge] edge to convert line.
def self.from_edge(edge, units, model_preferences)
dictionaries = {}
if model_preferences[:include_entity_attributes] && model_preferences[:include_edge_entity_attributes]
dictionaries = SketchupModel::Dictionary::DictionaryHandler.attribute_dictionaries_to_speckle(edge)
end
def self.from_edge(edge, units, model_preferences, global_transformation: nil)
dictionaries = SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_speckle(edge, model_preferences)
att = dictionaries.any? ? { dictionaries: dictionaries } : {}
speckle_schema = SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.speckle_schema_to_speckle(edge)
start_pt = Geometry::Point.from_vertex(edge.start.position, units)
end_pt = Geometry::Point.from_vertex(edge.end.position, units)
domain = Primitive::Interval.from_numeric(0, Float(edge.length), units)
bbox = Geometry::BoundingBox.from_bounds(edge.bounds, units)
Line.new(
start_pt: start_pt,
end_pt: end_pt,
domain: domain,
bbox: bbox,
units: units,
layer: SketchupModel::Query::Layer.entity_path(edge),
sketchup_attributes: att,
speckle_schema: speckle_schema,
application_id: edge.persistent_id.to_s
)
end
# @param _state [States::State] state of the application.
# @param edge [Sketchup::Face] face to get base line from face.
def self.base_line_from_face(face, units, global_transformation: nil)
points = face.vertices.collect(&:position)
points_z_values = points.collect(&:z)
height = Geometry.length_to_speckle(points_z_values.max - points_z_values.min, units)
min_z = points_z_values.min
projected_points = points.map { |p| Geom::Point3d.new(p.x, p.y, min_z) }
distance_with_points = Struct.new(:distance, :point_1, :point_2)
lines_with_distances = []
projected_points.each do |p|
projected_points.each do |p_other|
next if p_other == p
lines_with_distances.append(distance_with_points.new(p.distance(p_other), p, p_other))
end
end
lines_with_distances.sort_by!(&:distance).reverse!
p_1 = lines_with_distances.first.point_1
p_2 = lines_with_distances.first.point_2
unless global_transformation.nil?
p_1 = p_1.transform!(global_transformation)
p_2 = p_2.transform!(global_transformation)
end
Line.new(
start_pt: Geometry::Point.from_vertex(p_1, units),
end_pt: Geometry::Point.from_vertex(p_2, units),
domain: Primitive::Interval.from_numeric(0, Geometry.length_to_speckle(p_1.distance(p_2), units), units),
units: units,
layer: SketchupModel::Query::Layer.entity_path(face),
sketchup_attributes: {},
speckle_schema: {},
application_id: face.persistent_id.to_s
)
end
# @param state [States::State] state of the application.
# @param line [Object] object represents Speckle line.
# @param layer [Sketchup::Layer] layer to add {Sketchup::Edge} into it.
# @param entities [Sketchup::Entities] entities collection to add {Sketchup::Edge} into it.
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/CyclomaticComplexity
def self.to_native(state, line, layer, entities, &_convert_to_native)
if line.key?('value')
values = line['value']
@@ -75,25 +113,28 @@ module SpeckleConnector
end_pt = Point.to_native(line['end']['x'], line['end']['y'], line['end']['z'], line['units'])
edges = entities.add_edges(start_pt, end_pt)
end
line_layer_name = SketchupModel::Query::Layer.entity_layer_from_path(line['layer'])
line_layer = state.sketchup_state.sketchup_model.layers.to_a.find { |l| l.display_name == line_layer_name }
edges.each do |edge|
edge.layer = layer
edge.layer = line_layer.nil? ? layer : line_layer
unless line['sketchup_attributes'].nil?
SketchupModel::Dictionary::DictionaryHandler
SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_native(edge, line['sketchup_attributes']['dictionaries'])
end
end
return state, edges
end
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/CyclomaticComplexity
def self.test_line(start_point, end_point, units)
domain = Primitive::Interval.from_numeric(0, 5, units)
bbox = Geometry::BoundingBox.test_bounds(units)
Line.new(
start_pt: start_point,
end_pt: end_point,
domain: domain,
bbox: bbox,
layer: 'test',
application_id: '',
units: units
)
@@ -3,8 +3,14 @@
require_relative '../base'
require_relative '../geometry/bounding_box'
require_relative '../other/render_material'
require_relative '../../mapper/mapper'
require_relative '../../sketchup_model/query/entity'
require_relative '../../convertors/clean_up'
require_relative '../../sketchup_model/dictionary/base_dictionary_handler'
require_relative '../../sketchup_model/dictionary/speckle_schema_dictionary_handler'
require_relative '../../sketchup_model/dictionary/dictionary_handler'
require_relative '../../sketchup_model/utils/plane_utils'
require_relative '../../sketchup_model/query/layer'
module SpeckleConnector
module SpeckleObjects
@@ -29,7 +35,8 @@ module SpeckleConnector
# @param faces [Array] faces of the speckle mesh.
# @param sketchup_attributes [Hash] additional information about speckle mesh.
# rubocop:disable Metrics/ParameterLists
def initialize(units:, render_material:, vertices:, faces:, sketchup_attributes:, application_id: nil)
def initialize(units:, render_material:, vertices:, faces:,
sketchup_attributes:, layer:, speckle_schema: {}, application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
@@ -40,14 +47,35 @@ module SpeckleConnector
@polygons = []
@units = units
self[:units] = units
self[:layer] = layer
self[:renderMaterial] = render_material
# self[:bbox] = bbox
self[:'@(31250)vertices'] = vertices
self[:'@(62500)faces'] = faces
self[:sketchup_attributes] = sketchup_attributes if sketchup_attributes.any?
self['@SpeckleSchema'] = speckle_schema if speckle_schema.any?
end
# rubocop:enable Metrics/ParameterLists
# Checks 4 points are planar or not.
def self.check_4_points_planar(points)
plane = SketchupModel::Utils::Plane.from_points(points[0], points[1], points[2])
plane.on_plane?(points[3])
end
# Add quad mesh to sketchup native mesh by checking planarity.
# @param native_mesh [Geom::Mesh] sketchup mesh to convert them later faces.
# @param polygon_points [Array<Geom::Point3d>] sketchup points to add them with polygon to mesh.
def self.add_quad_mesh(native_mesh, polygon_points)
is_planar = check_4_points_planar(polygon_points)
if is_planar
native_mesh.add_polygon(polygon_points)
else
native_mesh.add_polygon([polygon_points[0], polygon_points[1], polygon_points[2]])
native_mesh.add_polygon([polygon_points[0], polygon_points[2], polygon_points[3]])
end
is_planar
end
# @param entities [Sketchup::Entities] entities to add
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
@@ -63,15 +91,23 @@ module SpeckleConnector
# Initialize native PolygonMesh object later to add polygon inside it.
native_mesh = Geom::PolygonMesh.new(mesh['vertices'].count / 3)
faces = mesh['faces']
has_any_non_planar_quad_mesh = false
while faces.count > 0
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] })
polygon_points = indices.map { |index| points[index] }
# Quad mesh
if num_pts == 4
is_planar = add_quad_mesh(native_mesh, polygon_points)
has_any_non_planar_quad_mesh = true unless is_planar
else
native_mesh.add_polygon(polygon_points)
end
end
state, _materials = Other::RenderMaterial.to_native(state, mesh['renderMaterial'],
layer, entities, &convert_to_native)
state, _materials = Other::RenderMaterial.to_native(state, mesh['renderMaterial'], layer,
entities, &convert_to_native)
# Find and assign material if exist
unless mesh['renderMaterial'].nil?
material_name = mesh['renderMaterial']['name'] || mesh['renderMaterial']['id']
@@ -82,17 +118,23 @@ module SpeckleConnector
# Add faces from mesh with material and smooth setting
entities.add_faces_from_mesh(native_mesh, smooth_flags, material, material)
added_faces = entities.grep(Sketchup::Face).last(native_mesh.polygons.length)
mesh_layer_name = SketchupModel::Query::Layer.entity_layer_from_path(mesh['layer'])
mesh_layer = state.sketchup_state.sketchup_model.layers.to_a.find { |l| l.display_name == mesh_layer_name }
# Merge only added faces in this scope
# if model_preferences[:merge_coplanar_faces]
# added_faces = Converters::CleanUp.merge_coplanar_faces(added_faces)
# end
added_faces.each do |face|
face.layer = layer
face.layer = mesh_layer unless mesh_layer.nil?
# Smooth edges if they already soft
# FIXME: Below line should be reconsidered. It might be a good to know here mesh comes from NURBS or not.
face.edges.each { |edge| edge.smooth = true if edge.soft? } if has_any_non_planar_quad_mesh
unless mesh['sketchup_attributes'].nil?
SketchupModel::Dictionary::DictionaryHandler
SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_native(face, mesh['sketchup_attributes']['dictionaries'])
end
end
# Merge only added faces in this scope
if model_preferences[:merge_coplanar_faces]
added_faces = Converters::CleanUp.merge_coplanar_faces(added_faces)
end
return state, added_faces
end
# rubocop:enable Metrics/MethodLength
@@ -101,37 +143,45 @@ module SpeckleConnector
# rubocop:enable Metrics/PerceivedComplexity:
# @param face [Sketchup::Face] face to convert mesh
# @param units [String] model units to send Speckle.
# @param model_preferences [Hash{Symbol=>Boolean}] model preferences to check include attributes or not.
# @param global_transform [Geom::Transformation, nil] global transformation value of face if it is not included
# into any component.
# rubocop:disable Style/MultilineTernaryOperator
# rubocop:disable Metrics/CyclomaticComplexity
def self.from_face(face, units, model_preferences)
dictionaries = {}
if model_preferences[:include_entity_attributes] && model_preferences[:include_face_entity_attributes]
dictionaries = SketchupModel::Dictionary::DictionaryHandler.attribute_dictionaries_to_speckle(face)
end
def self.from_face(speckle_state:, face:, units:, model_preferences:, global_transform: nil, parent_material: nil)
dictionaries = SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_speckle(face, model_preferences)
has_any_soften_edge = face.edges.any?(&:soft?)
att = dictionaries.any? ? { is_soften: has_any_soften_edge, dictionaries: dictionaries }
: { is_soften: has_any_soften_edge }
speckle_schema = Mapper.to_speckle(speckle_state, face, units, global_transformation: global_transform)
material = face.material || face.back_material || parent_material
speckle_mesh = Mesh.new(
units: units,
render_material: face.material.nil? && face.back_material.nil? ? nil : Other::RenderMaterial
.from_material(face.material || face.back_material),
# bbox: Geometry::BoundingBox.from_bounds(face.bounds, units),
vertices: [],
faces: [],
sketchup_attributes: att,
render_material: material.nil? ? nil : Other::RenderMaterial.from_material(material),
vertices: [], faces: [], sketchup_attributes: att,
layer: SketchupModel::Query::Layer.entity_path(face),
speckle_schema: speckle_schema,
application_id: face.persistent_id
)
speckle_mesh.face_to_mesh(face)
speckle_mesh.face_to_mesh(face, global_transform)
speckle_mesh.update_mesh
speckle_mesh
end
# rubocop:enable Style/MultilineTernaryOperator
# rubocop:enable Metrics/CyclomaticComplexity
def face_to_mesh(face)
# @param global_transform [Geom::Transformation, nil] global transformation value of face if it is not included
# into any component. So it's mesh will be transformed into global coordinates to represent it correctly in
# Speckle viewer or other connectors.
def face_to_mesh(face, global_transform = nil)
mesh = face.loops.count > 1 ? face.mesh : nil
mesh.nil? ? face_vertices_to_array(face) : mesh_points_to_array(mesh)
mesh.nil? ? face_indices_to_array(face) : mesh_faces_to_array(mesh)
if global_transform.nil?
mesh.nil? ? face_vertices_to_array(face) : mesh_points_to_array(mesh)
mesh.nil? ? face_indices_to_array(face) : mesh_faces_to_array(mesh)
else
mesh_points_to_array(face.mesh, global_transform)
mesh_faces_to_array(face.mesh, global_transform)
end
end
# Collects indexed Sketchup vertices into flat array for Speckle use.
@@ -176,7 +226,8 @@ module SpeckleConnector
# Get a flat array of vertices from a sketchup polygon mesh
# @param mesh [Geom::PolygonMesh] mesh to get points.
def mesh_points_to_array(mesh)
def mesh_points_to_array(mesh, global_transform = nil)
mesh.transform!(global_transform) unless global_transform.nil?
mesh.points.each do |pt|
# FIXME: Enable previous line when viewer supports shared vertices
# vertices.push(pt) unless vertices.any? { |point| point == pt }
@@ -186,7 +237,8 @@ module SpeckleConnector
# Get an array of face indices from a sketchup polygon mesh
# @param mesh [Geom::PolygonMesh] mesh to convert into polygons.
def mesh_faces_to_array(mesh)
def mesh_faces_to_array(mesh, global_transform = nil)
mesh.transform!(global_transform) unless global_transform.nil?
mesh.polygons.each do |poly|
global_polygon_array = [poly.count]
poly.each do |index|
@@ -225,6 +277,39 @@ module SpeckleConnector
end
points
end
# Mesh group id helps to determine how to group faces into meshes.
# @param face [Sketchup::Face] face to get mesh group id.
def self.get_mesh_group_id(face, model_preferences, parent_material = nil)
if model_preferences[:include_entity_attributes] &&
model_preferences[:include_face_entity_attributes] &&
attribute_dictionary?(face)
return face.persistent_id.to_s
end
material = face.material || face.back_material || parent_material
layer_name = face.layer.display_name
return layer_name if material.nil?
return material.entityID.to_s + layer_name
end
def self.attribute_dictionary?(face)
any_attribute_dictionary = !(face.attribute_dictionaries.nil? || face.attribute_dictionaries.first.nil?)
return any_attribute_dictionary unless any_attribute_dictionary
# If there are any attribute dictionary, then make sure that they are not ignored ones.
all_attribute_dictionary_ignored = face.attribute_dictionaries.all? do |dict|
ignored_dictionaries.include?(dict.name)
end
!all_attribute_dictionary_ignored
end
def self.ignored_dictionaries
[
'Speckle_Base_Object'
]
end
end
end
end
@@ -0,0 +1,21 @@
# frozen_string_literal: true
require_relative '../base'
require_relative '../../constants/type_constants'
module SpeckleConnector
module SpeckleObjects
module Geometry
# Polycurve object definition for Speckle.
# It basically groups the lines-curves under it's `segments` property.
class Polycurve < Base
SPECKLE_TYPE = OBJECTS_GEOMETRY_POLYCURVE
def self.to_native(state, polycurve, layer, entities, &convert_to_native)
polycurve['displayValue'] = polycurve['segments']
convert_to_native.call(state, polycurve, layer, entities)
end
end
end
end
end
@@ -0,0 +1,65 @@
# frozen_string_literal: true
require_relative 'length'
require_relative 'point'
require_relative 'bounding_box'
require_relative '../base'
require_relative '../primitive/interval'
module SpeckleConnector
module SpeckleObjects
module Geometry
# Polyline object definition for Speckle.
class Polyline < Base
SPECKLE_TYPE = OBJECTS_GEOMETRY_POLYLINE
# @param value [Array<Numeric>] polygon vertex coordinates as flat list.
# @param domain [Primitive::Interval] domain of the polyline.
# @param length [Numeric] length of the polyline.
# @param closed [Boolean] whether polyline is closed or not.
# @param units [String] units of the polyline.
# @param application_id [String] application id of the polyline which corresponds to persistent_id of the Loop.
# rubocop:disable Metrics/ParameterLists
def initialize(value:, domain:, length:, closed:, units:, application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: application_id,
id: nil
)
self[:value] = value
self[:domain] = domain
self[:length] = length
self[:closed] = closed
self[:units] = units
end
# rubocop:enable Metrics/ParameterLists
# @param loop [Sketchup::Loop] loop to convert closed speckle polyline.
def self.from_loop(loop, units, global_transformation: nil)
points = loop.vertices.collect do |vertex|
position = vertex.position
position = vertex.position.transform!(global_transformation) unless global_transformation.nil?
position
end
values = points.collect do |p|
[Geometry.length_to_speckle(p.x, units),
Geometry.length_to_speckle(p.y, units),
Geometry.length_to_speckle(p.z, units)]
end.flatten
loop_length = loop.edges.sum(&:length)
length = Geometry.length_to_speckle(loop_length, units)
domain = Primitive::Interval.from_lengths(0, loop_length, units)
Polyline.new(
value: values,
domain: domain,
length: length,
units: units,
closed: true,
application_id: loop.persistent_id.to_s
)
end
end
end
end
end
@@ -1,5 +1,6 @@
# frozen_string_literal: true
require_relative 'length'
require_relative '../base'
module SpeckleConnector
@@ -25,6 +26,14 @@ module SpeckleConnector
self[:z] = z
self[:units] = units
end
def self.to_native(x, y, z, units)
Geom::Vector3d.new(
Geometry.length_to_native(x, units),
Geometry.length_to_native(y, units),
Geometry.length_to_native(z, units)
)
end
end
end
end
@@ -0,0 +1,84 @@
# frozen_string_literal: true
require_relative '../base'
require_relative '../other/transform'
require_relative '../other/block_definition'
require_relative '../other/block_instance'
require_relative '../../constants/type_constants'
require_relative '../../sketchup_model/dictionary/dictionary_handler'
module SpeckleConnector
module SpeckleObjects
module GIS
# BoundingBox object definition for Speckle.
class PolygonElement < Base
SPECKLE_TYPE = OBJECTS_GIS_POLYGONELEMENT
def self.get_definition_name(obj, attributes)
return obj['name'] unless obj['name'].nil?
return attributes['name'] unless attributes['name'].nil?
return "def::#{obj['id']}"
end
def self.get_qgis_attributes(obj)
attributes = obj['attributes'].to_h
speckle_properties = %w[id speckle_type totalChildrenCount units applicationId]
speckle_properties.each { |key| attributes.delete(key) }
attributes
end
# Handles polygon element differently from display value.
def self.to_native(state, obj, layer, entities, &convert_to_native)
attributes = get_qgis_attributes(obj)
obj = collect_definition_geometries(obj)
obj['name'] = get_definition_name(obj, attributes)
state, _definitions = Other::BlockDefinition.to_native(
state, obj, layer, entities, &convert_to_native
)
definition = state.sketchup_state.sketchup_model
.definitions[Other::BlockDefinition.get_definition_name(obj)]
Other::BlockInstance.find_and_erase_existing_instance(definition, obj['id'], obj['applicationId'])
t_arr = obj['transform']
transform = t_arr.nil? ? Geom::Transformation.new : Other::Transform.to_native(t_arr, obj['units'])
instance = entities.add_instance(definition, transform)
instance.name = obj['name'] unless obj['name'].nil?
SketchupModel::Dictionary::DictionaryHandler.set_hash(instance, attributes, 'qgis')
SketchupModel::Dictionary::DictionaryHandler.set_hash(definition, attributes, 'qgis')
# Align instance axes that created from display value. (without any transform)
Other::BlockInstance.align_instance_axes(instance)
return state, [instance, definition]
end
def self.collect_definition_geometries(obj)
geometries = []
# FIXME: This type check needed because of QGIS. It can send geometries both way, object or array..
# This is something need to be fixed by QGIS.
if obj['geometry'].is_a?(Array)
obj['geometry'].each do |geometry|
display_value = geometry['displayValue']
geometries += display_value
end
else
geometries += obj['geometry']['displayValue']
end
geometries.each do |geo|
if geo['speckle_type'] && geo['speckle_type'] == OBJECTS_GEOMETRY_MESH
geo['sketchup_attributes'] = { 'is_soften' => false }
end
end
obj['geometry'] = geometries
obj
end
end
end
end
end
@@ -7,7 +7,8 @@ require_relative '../base'
require_relative '../geometry/point'
require_relative '../geometry/mesh'
require_relative '../geometry/bounding_box'
require_relative '../../sketchup_model/dictionary/dictionary_handler'
require_relative '../../sketchup_model/dictionary/base_dictionary_handler'
require_relative '../../sketchup_model/dictionary/speckle_schema_dictionary_handler'
module SpeckleConnector
module SpeckleObjects
@@ -22,7 +23,7 @@ module SpeckleConnector
# @param application_id [String, NilClass] application id of the block definition.
# rubocop:disable Metrics/ParameterLists
def initialize(geometry:, name:, units:, always_face_camera:, sketchup_attributes: {},
application_id: nil)
speckle_schema: {}, application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
@@ -33,7 +34,8 @@ module SpeckleConnector
self[:name] = name
self[:always_face_camera] = always_face_camera
self[:sketchup_attributes] = sketchup_attributes if sketchup_attributes.any?
# FIXME: Since geometry sends with @ as detached, block basePlane renders on viewer.
self[:SpeckleSchema] = speckle_schema if speckle_schema.any?
# '@@' means that it is a detached property.
self['@@geometry'] = geometry
end
# rubocop:enable Metrics/ParameterLists
@@ -42,32 +44,19 @@ module SpeckleConnector
# instance
# @param units [String] units of the Sketchup model
# @param definitions [Hash{String=>BlockDefinition}] all converted {BlockDefinition}s on the converter.
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/AbcSize
# rubocop:disable Metrics/ParameterLists
def self.from_definition(definition, units, definitions, preferences, speckle_state, parent, &convert)
guid = definition.guid
return definitions[guid] if definitions.key?(guid)
dictionaries = {}
if preferences[:model][:include_entity_attributes]
if definition.group?
if preferences[:model][:include_group_entity_attributes]
dictionaries = SketchupModel::Dictionary::DictionaryHandler
.attribute_dictionaries_to_speckle(definition)
end
elsif preferences[:model][:include_component_entity_attributes]
dictionaries = SketchupModel::Dictionary::DictionaryHandler.attribute_dictionaries_to_speckle(definition)
end
end
def self.from_definition(definition, units, preferences, speckle_state, parent, &convert)
dictionaries = SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_speckle(definition, preferences[:model])
att = dictionaries.any? ? { dictionaries: dictionaries } : {}
speckle_schema = SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler
.speckle_schema_to_speckle(definition)
# TODO: Solve logic
geometry = if definition.entities[0].is_a?(Sketchup::Edge) || definition.entities[0].is_a?(Sketchup::Face)
new_speckle_state, geo = group_entities_to_speckle(
definition, preferences, speckle_state, parent, &convert
definition.entities, preferences, speckle_state, parent, &convert
)
speckle_state = new_speckle_state
geo
@@ -83,21 +72,18 @@ module SpeckleConnector
end
end
# FIXME: Decide how to approach base point of the definition instead origin.
block_definition = BlockDefinition.new(
units: units,
name: definition.name,
geometry: geometry,
geometry: geometry.compact,
always_face_camera: definition.behavior.always_face_camera?,
sketchup_attributes: att,
speckle_schema: speckle_schema,
application_id: definition.persistent_id.to_s
)
return speckle_state, block_definition
end
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/MethodLength
# rubocop:enable Metrics/AbcSize
# rubocop:enable Metrics/ParameterLists
def self.get_definition_name(def_obj)
@@ -120,7 +106,12 @@ module SpeckleConnector
definition_name = get_definition_name(definition_obj)
application_id = definition_obj['applicationId']
definition = sketchup_model.definitions[definition_name]
if definition && (definition.name == definition_name || definition.guid == application_id)
# Check any entities of definition changed
entities_updated = entities_updated?(definition, definition_obj)
if definition && !entities_updated &&
(definition.name == definition_name || definition.guid == application_id)
return state, [definition]
end
@@ -130,21 +121,30 @@ module SpeckleConnector
sketchup_attributes = definition_obj['sketchup_attributes']
definition&.entities&.clear!
definition ||= sketchup_model.definitions.add(definition_name)
definition.layer = layer
ngon_faces = []
if geometry.is_a?(Array)
geometry.each do |obj|
state = convert_to_native.call(state, obj, layer, definition.entities)
state, added_entities = convert_to_native.call(state, obj, layer, definition.entities)
if added_entities.length == 1 && added_entities.first.is_a?(Sketchup::Face)
ngon_faces.append(added_entities.first)
end
end
end
ngon_faces.each do |f|
f.edges.each do |e|
e.soft = false
e.smooth = false
end
end
if geometry.is_a?(Hash) && !definition_obj['speckle_type'].nil?
state = convert_to_native.call(state, geometry, layer, definition.entities)
state, _converted_entities = convert_to_native.call(state, geometry, layer, definition.entities)
end
# puts("definition finished: #{name} (#{application_id})")
# puts(" entity count: #{definition.entities.count}")
definition.behavior.always_face_camera = always_face_camera
unless sketchup_attributes.nil?
SketchupModel::Dictionary::DictionaryHandler
SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_native(definition, sketchup_attributes['dictionaries'])
end
return state, [definition]
@@ -158,21 +158,22 @@ module SpeckleConnector
# rubocop:disable Metrics/MethodLength
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
def self.group_entities_to_speckle(definition, preferences, speckle_state, parent, &convert)
orphan_edges = definition.entities.grep(Sketchup::Edge).filter { |edge| edge.faces.none? }
def self.group_entities_to_speckle(entities, preferences, speckle_state, parent, &convert)
entities = entities.reject(&:hidden?)
orphan_edges = entities.grep(Sketchup::Edge).filter { |edge| edge.faces.none? }
lines = orphan_edges.collect do |orphan_edge|
new_speckle_state, converted = convert.call(orphan_edge, preferences, speckle_state, parent)
speckle_state = new_speckle_state
converted
end
nested_blocks = definition.entities.grep(Sketchup::ComponentInstance).collect do |component_instance|
nested_blocks = entities.grep(Sketchup::ComponentInstance).collect do |component_instance|
new_speckle_state, converted = convert.call(component_instance, preferences, speckle_state, parent)
speckle_state = new_speckle_state
converted
end
nested_groups = definition.entities.grep(Sketchup::Group).collect do |group|
nested_groups = entities.grep(Sketchup::Group).collect do |group|
new_speckle_state, converted = convert.call(group, preferences, speckle_state, parent)
speckle_state = new_speckle_state
converted
@@ -180,7 +181,9 @@ module SpeckleConnector
if preferences[:model][:combine_faces_by_material]
mesh_groups = {}
definition.entities.grep(Sketchup::Face).collect do |face|
entities.grep(Sketchup::Face).collect do |face|
next unless SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.attribute_dictionary(face).nil?
new_speckle_state = group_meshes_by_material(
face, mesh_groups, speckle_state, preferences, parent, &convert
)
@@ -192,7 +195,7 @@ module SpeckleConnector
return speckle_state, lines + nested_blocks + nested_groups + mesh_groups.values
else
meshes = []
definition.entities.grep(Sketchup::Face).collect do |face|
entities.grep(Sketchup::Face).collect do |face|
new_speckle_state, converted = convert.call(face, preferences, speckle_state, parent)
meshes.append(converted)
speckle_state = new_speckle_state
@@ -209,7 +212,7 @@ module SpeckleConnector
# rubocop:disable Metrics/ParameterLists
def self.group_meshes_by_material(face, mesh_groups, speckle_state, preferences, parent, &convert)
# convert material
mesh_group_id = get_mesh_group_id(face, preferences[:model])
mesh_group_id = Geometry::Mesh.get_mesh_group_id(face, preferences[:model])
new_speckle_state, converted = convert.call(face, preferences, speckle_state, parent)
mesh_groups[mesh_group_id] = converted unless mesh_groups.key?(mesh_group_id)
mesh_group = mesh_groups[mesh_group_id]
@@ -219,36 +222,17 @@ module SpeckleConnector
end
# rubocop:enable Metrics/ParameterLists
# Mesh group id helps to determine how to group faces into meshes.
# @param face [Sketchup::Face] face to get mesh group id.
def self.get_mesh_group_id(face, model_preferences)
if model_preferences[:include_entity_attributes] &&
model_preferences[:include_face_entity_attributes] &&
attribute_dictionary?(face)
return face.persistent_id.to_s
# It is important check for hosted elements that wrapped into component in sketchup.
# Their definition name might be stay same but their speckle ids should be checked
# to compare they updated or not.
def self.entities_updated?(definition, speckle_definition)
children_changed = false
unless definition.nil?
# TODO: Here we need to check later if definition invalid or not.
previous_speckle_id = definition.get_attribute(SPECKLE_BASE_OBJECT, 'speckle_id')
children_changed = previous_speckle_id != speckle_definition['id']
end
material = face.material || face.back_material
return 'none' if material.nil?
return material.entityID.to_s
end
def self.attribute_dictionary?(face)
any_attribute_dictionary = !(face.attribute_dictionaries.nil? || face.attribute_dictionaries.first.nil?)
return any_attribute_dictionary unless any_attribute_dictionary
# If there are any attribute dictionary, then make sure that they are not ignored ones.
all_attribute_dictionary_ignored = face.attribute_dictionaries.all? do |dict|
ignored_dictionaries.include?(dict.name)
end
!all_attribute_dictionary_ignored
end
def self.ignored_dictionaries
[
'Speckle_Base_Object'
]
children_changed
end
end
end
@@ -5,7 +5,9 @@ require_relative 'transform'
require_relative 'block_definition'
require_relative '../base'
require_relative '../geometry/bounding_box'
require_relative '../../sketchup_model/dictionary/dictionary_handler'
require_relative '../../sketchup_model/dictionary/base_dictionary_handler'
require_relative '../../sketchup_model/dictionary/speckle_schema_dictionary_handler'
require_relative '../../sketchup_model/query/layer'
module SpeckleConnector
module SpeckleObjects
@@ -23,8 +25,8 @@ module SpeckleConnector
# @param sketchup_attributes [Hash{Symbol=>Object}] sketchup attributes of the block instance.
# @param application_id [String] application id of the block instance.
# rubocop:disable Metrics/ParameterLists
def initialize(units:, is_sketchup_group:, name:, render_material:, transform:, block_definition:,
sketchup_attributes: {}, application_id: nil)
def initialize(units:, is_sketchup_group:, name:, render_material:, transform:, block_definition:, layer:,
sketchup_attributes: {}, speckle_schema: {}, application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
@@ -33,10 +35,12 @@ module SpeckleConnector
)
self[:units] = units
self[:name] = name
self[:layer] = layer
self[:is_sketchup_group] = is_sketchup_group
self[:renderMaterial] = render_material
self[:transform] = transform
self[:sketchup_attributes] = sketchup_attributes if sketchup_attributes.any?
self[:SpeckleSchema] = speckle_schema if speckle_schema.any?
# FIXME: Since blockDefinition sends with @ as detached, block basePlane renders on viewer.
self['@@definition'] = block_definition
end
@@ -47,12 +51,10 @@ module SpeckleConnector
new_speckle_state, block_definition = convert.call(group.definition, preferences, speckle_state,
group.persistent_id)
speckle_state = new_speckle_state
dictionaries = {}
if preferences[:model][:include_entity_attributes] && preferences[:model][:include_group_entity_attributes]
dictionaries = SketchupModel::Dictionary::DictionaryHandler.attribute_dictionaries_to_speckle(group)
end
dictionaries = SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_speckle(group, preferences[:model])
att = dictionaries.any? ? { dictionaries: dictionaries } : {}
speckle_schema = SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler.speckle_schema_to_speckle(group)
block_instance = BlockInstance.new(
units: units,
is_sketchup_group: true,
@@ -60,7 +62,9 @@ module SpeckleConnector
render_material: group.material.nil? ? nil : RenderMaterial.from_material(group.material),
transform: Other::Transform.from_transformation(group.transformation, units),
block_definition: block_definition,
layer: SketchupModel::Query::Layer.entity_path(group),
sketchup_attributes: att,
speckle_schema: speckle_schema,
application_id: group.guid
)
return speckle_state, block_instance
@@ -77,13 +81,11 @@ module SpeckleConnector
)
speckle_state = new_speckle_state
dictionaries = {}
if preferences[:model][:include_entity_attributes] &&
preferences[:model][:include_component_entity_attributes]
dictionaries = SketchupModel::Dictionary::DictionaryHandler
.attribute_dictionaries_to_speckle(component_instance)
end
dictionaries = SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_speckle(component_instance, preferences[:model])
att = dictionaries.any? ? { dictionaries: dictionaries } : {}
speckle_schema = SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler
.speckle_schema_to_speckle(component_instance)
block_instance = BlockInstance.new(
units: units,
@@ -96,7 +98,9 @@ module SpeckleConnector
end,
transform: Other::Transform.from_transformation(component_instance.transformation, units),
block_definition: block_definition,
layer: SketchupModel::Query::Layer.entity_path(component_instance),
sketchup_attributes: att,
speckle_schema: speckle_schema,
application_id: component_instance.persistent_id.to_s
)
return speckle_state, block_instance
@@ -128,7 +132,10 @@ module SpeckleConnector
definition = state.sketchup_state.sketchup_model
.definitions[BlockDefinition.get_definition_name(block_definition)]
return add_instance_from_definition(state, block, layer, entities, definition, is_group, &convert_to_native)
block_layer_name = SketchupModel::Query::Layer.entity_layer_from_path(block['layer'])
block_layer = state.sketchup_state.sketchup_model.layers.to_a.find { |l| l.display_name == block_layer_name }
return add_instance_from_definition(state, block, block_layer, layer, entities, definition, is_group,
&convert_to_native)
end
def self.get_transform_matrix(block)
@@ -163,18 +170,19 @@ module SpeckleConnector
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/PerceivedComplexity
# rubocop:disable Metrics/ParameterLists
def self.add_instance_from_definition(state, block, layer, entities, definition, is_group, &convert_to_native)
def self.add_instance_from_definition(state, block, block_layer, layer, entities, definition, is_group,
&convert_to_native)
t_arr = get_transform_matrix(block)
transform = Other::Transform.to_native(t_arr, block['units'])
instance = if is_group
# rubocop:disable SketchupSuggestions/AddGroup
group = entities.add_group(definition.entities.to_a)
group.layer = layer
group.layer = block_layer.nil? ? layer : block_layer
group
# rubocop:enable SketchupSuggestions/AddGroup
else
instance = entities.add_instance(definition, transform)
instance.layer = layer
instance.layer = block_layer.nil? ? layer : block_layer
instance
end
@@ -184,8 +192,8 @@ module SpeckleConnector
puts("Failed to create instance for speckle block instance #{block['id']}") if instance.nil?
# Transform already applied to instance unless is group
instance.transformation = transform if is_group
state, _materials = Other::RenderMaterial.to_native(state, block['renderMaterial'],
layer, entities, &convert_to_native)
state, _materials = Other::RenderMaterial.to_native(state, block['renderMaterial'], layer,
entities, &convert_to_native)
# Retrieve material from state
unless block['renderMaterial'].nil?
@@ -196,7 +204,7 @@ module SpeckleConnector
instance.name = block['name'] unless block['name'].nil?
unless block['sketchup_attributes'].nil?
SketchupModel::Dictionary::DictionaryHandler
SketchupModel::Dictionary::BaseDictionaryHandler
.attribute_dictionaries_to_native(instance, block['sketchup_attributes']['dictionaries'])
end
return state, [instance, definition]
@@ -206,6 +214,20 @@ module SpeckleConnector
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/PerceivedComplexity
# rubocop:enable Metrics/ParameterLists
# Instances that created from display value that has no any transform value.
# Because of this reason their definition created with origin axis. We basically create transformation
# vector between bounds min to origin, to move definition axis to bounds min. Otherwise they looks weird in
# sketchup and might be cumbersome when we want to add new entities into definition.
# @param instance [Sketchup::ComponentInstance] instance to align axis to it's bounds
def self.align_instance_axes(instance)
bounds = instance.bounds
transform = Geom::Transformation.translation(bounds.min.vector_to(Geom::Point3d.new(0, 0, 0)))
entities = instance.definition.entities
entities.transform_entities(transform, entities.to_a)
instance_transform = instance.transformation
instance.transform!(instance_transform * transform.inverse * instance_transform.inverse)
end
end
end
end
@@ -2,7 +2,7 @@
module SpeckleConnector
module SpeckleObjects
module Others
module Other
# Color object transformations between sketchup and speckle.
class Color
# @param color [Sketchup::Color] color to convert speckle object
@@ -16,9 +16,24 @@ module SpeckleConnector
end
def self.to_native(speckle_color)
return from_int(speckle_color) if speckle_color.is_a?(Numeric)
Sketchup::Color.new(speckle_color['red'], speckle_color['green'],
speckle_color['blue'], speckle_color['alpha'])
end
# @param color [Sketchup::Color] color to convert speckle object
# @return [Numeric] int value of the color
def self.to_int(color)
rgba = color.to_a
[rgba[3] << 24 | rgba[0] << 16 | rgba[1] << 8 | rgba[2]].pack('l').unpack1('l').to_i
end
# @param argb [Numeric] int value of the corresponding color
# @return [Sketchup::Color] sketchup color
def self.from_int(argb)
Sketchup::Color.new((argb >> 16) & 255, (argb >> 8) & 255, argb & 255, (argb >> 24) & 255)
end
end
end
end
@@ -0,0 +1,35 @@
# frozen_string_literal: true
require_relative 'color'
require_relative '../base'
require_relative '../../constants/type_constants'
module SpeckleConnector
module SpeckleObjects
module Other
# DisplayStyle object for layer.
class DisplayStyle < Base
def initialize(name:, color:, line_type:)
super(
speckle_type: OBJECTS_OTHER_DISPLAYSTYLE,
total_children_count: 0,
application_id: nil,
id: nil
)
self[:name] = name
self[:color] = color
self[:linetype] = line_type unless line_type.nil?
end
# @param layer [Sketchup::Layer] layer to get display style.
def self.from_layer(layer)
DisplayStyle.new(
name: '',
color: Color.to_int(layer.color),
line_type: layer.line_style.nil? ? nil : layer.line_style.name
)
end
end
end
end
end
@@ -7,7 +7,7 @@ require_relative '../../immutable/immutable'
require_relative '../../ext/immutable_ruby/hash'
require_relative '../base'
require_relative '../geometry/bounding_box'
require_relative '../../sketchup_model/dictionary/dictionary_handler'
require_relative '../../sketchup_model/dictionary/base_dictionary_handler'
module SpeckleConnector
module SpeckleObjects
@@ -22,7 +22,15 @@ module SpeckleConnector
return format_naming_convention([family, type, category, element_id]) unless element_id.nil?
return "def::#{def_obj['applicationId']}"
name = def_obj['name']
return "#{name}::#{def_obj['applicationId']}" if !name.nil? && !def_obj['applicationId'].nil?
return "#{name}::#{def_obj['id']}" unless name.nil?
speckle_type = def_obj['speckle_type'].split('.').last
return "#{speckle_type}::#{def_obj['applicationId']}" unless def_obj['applicationId'].nil?
return "#{speckle_type}::#{def_obj['id']}"
end
def self.format_naming_convention(entries)
@@ -39,6 +47,13 @@ module SpeckleConnector
name
end
# Get instance name as speckle_type if it is structured as `speckle_type::application_id`
def self.get_instance_name(definition_name)
return definition_name unless definition_name.include?('::')
definition_name.split('::').first
end
# Creates a component definition and instance from a speckle object with a display value
# @param state [States::State] state of the application.
def self.to_native(state, obj, layer, entities, &convert_to_native)
@@ -47,33 +62,40 @@ module SpeckleConnector
obj['name'] = get_definition_name(obj)
state, _definitions = BlockDefinition.to_native(
state,
obj,
layer,
entities,
&convert_to_native
state, obj, layer, entities, &convert_to_native
)
definition = state.sketchup_state.sketchup_model.definitions[BlockDefinition.get_definition_name(obj)]
BlockInstance.find_and_erase_existing_instance(definition, obj['id'], obj['applicationId'])
t_arr = obj['transform']
transform = t_arr.nil? ? Geom::Transformation.new : OTHER::Transform.to_native(t_arr, units)
transform = t_arr.nil? ? Geom::Transformation.new : Other::Transform.to_native(t_arr, obj['units'])
instance = entities.add_instance(definition, transform)
instance.name = obj['name'] unless obj['name'].nil?
instance.name = get_instance_name(obj['name']) unless obj['name'].nil?
instance.layer = layer unless layer.nil?
# Align instance axes that created from display value. (without any transform)
# BlockInstance.align_instance_axes(instance)
return state, [instance, definition]
end
def self.collect_definition_geometries(obj)
obj['geometry'] = obj['displayValue']
obj['geometry'] = obj['displayValue'] || obj['@displayValue']
if !obj['elements'].nil? && obj['elements'].is_a?(Array)
obj['elements'].each do |element|
# Mullions is a special case here, they are extracted as base object with @displayValue from revit..
if element['@displayValue'].nil?
obj['geometry'].append(element)
else
obj['geometry'] += element['@displayValue']
elements = obj['elements'] || obj['@elements']
# if only elements are there then assign only elements, there are some cases that RevitWalls can only
# have elements instead of display value
if obj['geometry'].nil? && !elements.nil?
obj['geometry'] = elements
else
if !elements.nil? && elements.is_a?(Array)
elements.each do |element|
# Mullions is a special case here, they are extracted as base object with @displayValue from revit..
if element['@displayValue'].nil?
obj['geometry'].append(element)
else
obj['geometry'] += element['@displayValue']
end
end
end
end
@@ -1,5 +1,6 @@
# frozen_string_literal: true
require_relative 'color'
require_relative '../base'
module SpeckleConnector
@@ -38,10 +39,9 @@ module SpeckleConnector
# @param material [Sketchup::Material] material on the Sketchup.
def self.from_material(material)
rgba = material.color.to_a
RenderMaterial.new(
name: material.name,
diffuse: [rgba[3] << 24 | rgba[0] << 16 | rgba[1] << 8 | rgba[2]].pack('l').unpack1('l'),
diffuse: Other::Color.to_int(material.color),
opacity: material.alpha,
emissive: -16_777_216,
metalness: 0,
@@ -65,7 +65,7 @@ module SpeckleConnector
material = sketchup_model.materials.add(name)
material.alpha = render_material['opacity']
argb = render_material['diffuse']
material.color = Sketchup::Color.new((argb >> 16) & 255, (argb >> 8) & 255, argb & 255, (argb >> 24) & 255)
material.color = Color.from_int(argb)
new_sketchup_state = state.sketchup_state.with_materials(materials.add_material(name, material))
return state.with_sketchup_state(new_sketchup_state), [material]
end
@@ -4,7 +4,7 @@ require_relative 'color'
module SpeckleConnector
module SpeckleObjects
module Others
module Other
# Rendering options for scenes.
class RenderingOptions
# @param options [Sketchup::RenderingOptions] rendering options to convert speckle object
@@ -0,0 +1,107 @@
# frozen_string_literal: true
require_relative '../base'
require_relative '../other/color'
module SpeckleConnector
module SpeckleObjects
module Relations
# Sketchup layer (tag) tree relation.
class Layer < Base
SPECKLE_TYPE = 'Speckle.Core.Models.Collection'
# rubocop:disable Metrics/ParameterLists
def initialize(name:, visible:, is_folder:, full_path: nil, line_style: nil, color: nil, layers_and_folders: [],
application_id: nil)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: application_id,
id: nil
)
self[:name] = name
self[:color] = color
self[:visible] = visible
self[:is_folder] = is_folder
self[:full_path] = full_path unless full_path.nil?
self[:line_style] = line_style unless line_style.nil?
self[:collectionType] = 'layer'
self[:elements] = layers_and_folders if layers_and_folders.any?
end
# rubocop:enable Metrics/ParameterLists
# @param speckle_layer [Object] speckle layer object.
# @param folder [Sketchup::Layers, Sketchup::LayerFolder] folder to create layers in it.
# @param sketchup_model [Sketchup::Model] sketchup active model.
def self.to_native_layer(speckle_layer, folder, sketchup_model)
layer = sketchup_model.layers.add_layer(speckle_layer[:name])
layer.visible = speckle_layer[:visible] unless speckle_layer[:visible].nil?
layer.color = SpeckleObjects::Other::Color.to_native(speckle_layer[:color]) if speckle_layer[:color]
if speckle_layer[:line_style]
line_style = sketchup_model.line_styles.find { |ls| ls.name == speckle_layer[:line_style] }
layer.line_style = line_style unless line_style.nil?
end
folder.add_layer(layer) if folder.is_a?(Sketchup::LayerFolder)
end
# Flat layer conversion.
def self.to_native_flat_layers(layers_relation, sketchup_model)
speckle_layers = layers_relation[:elements]
elements_to_layers(speckle_layers, sketchup_model)
end
# Converts elements to layers with it's full path.
def self.elements_to_layers(elements, sketchup_model)
elements.each do |element|
element[:name] = element[:full_path]
to_native_layer(element, sketchup_model.layers, sketchup_model)
elements_to_layers(element[:elements], sketchup_model) unless element[:elements].nil?
end
end
# Nested layer conversion with folders.
def self.to_native_layer_folder(layers_relation, folder, sketchup_model)
speckle_layers = layers_relation[:elements].select { |layer_or_fol| layer_or_fol[:elements].nil? }
speckle_layers.each do |speckle_layer|
to_native_layer(speckle_layer, folder, sketchup_model)
end
speckle_folders = layers_relation[:elements].reject { |layer_or_fol| layer_or_fol[:elements].nil? }
speckle_folders.each do |speckle_folder|
sub_folder = folder.add_folder(speckle_folder[:name])
sub_folder.visible = speckle_folder[:visible] unless speckle_folder[:visible].nil?
to_native_layer_folder(speckle_folder, sub_folder, sketchup_model)
end
end
# @param folder [Sketchup::LayerFolder] sketchup layer folder that might contains other folders and layers.
def self.from_folder(folder)
layers = folder.layers.collect { |layer| from_layer(layer) }
sub_folders = folder.folders.collect { |sub_folder| from_folder(sub_folder) }
Layer.new(
name: folder.display_name,
visible: folder.visible?,
is_folder: true,
layers_and_folders: layers + sub_folders,
application_id: folder.persistent_id
)
end
# @param layer [Sketchup::Layer] sketchup layer (tag) that objects can be assigned..
def self.from_layer(layer)
Layer.new(
name: layer.display_name,
visible: layer.visible?,
is_folder: false,
line_style: layer.line_style.nil? ? nil : layer.line_style.name,
color: SpeckleObjects::Other::Color.to_speckle(layer.color),
application_id: layer.persistent_id
)
end
end
end
end
end
@@ -0,0 +1,102 @@
# frozen_string_literal: true
require_relative 'layer'
require_relative '../base'
module SpeckleConnector
module SpeckleObjects
module Relations
# Sketchup layers (tag) tree relation. The difference between Layer is, this is the top object that holds
# properties for all layers or folders, i.e. active layer.
class Layers < Base
SPECKLE_TYPE = 'Speckle.Core.Models.Collection'
def initialize(active:, layers:)
super(
speckle_type: SPECKLE_TYPE,
total_children_count: 0,
application_id: nil,
id: nil
)
self[:active_layer] = active
self[:elements] = layers
end
# Extract relations from commit obj to create layers in advance.
# By doing this, also checks layers will be created as flat list or nested structure according to source app.
# @param commit_obj [Hash] commit object to extract layer relations.
# @param source_app [String] source application to decide layer creation strategy.
# Currently for
# - Revit: we don't create layers in advance because we create layers according to categories.
# - SketchUp: we create layers in advance as nested.
# - Rhino: we create layers in advance as flat list with it's full path.
def self.extract_relations(commit_obj, source_app)
return nil unless commit_obj['speckle_type'] == SPECKLE_CORE_MODELS_COLLECTION
elements = element_to_relation(commit_obj['elements'], source_app, [])
Layers.new(
active: commit_obj['active_layer'],
layers: elements
)
end
# rubocop:disable Metrics/CyclomaticComplexity
def self.element_to_relation(elements, source_app, parent_layers)
elements.collect do |element|
next unless element['speckle_type'] == SPECKLE_CORE_MODELS_COLLECTION
layers_tree = parent_layers.dup.append(element['name'])
full_path = ''
parent_layers.each { |parent| full_path += "#{parent}::" }
full_path += element['name']
# Add this info to commit object to check later layer_collection conversion
element['full_path'] = full_path if source_app.include?('rhino')
is_folder = element['elements'].any? { |e| e['speckle_type'] == SPECKLE_CORE_MODELS_COLLECTION }
color = element['color'] || element['displayStyle']['color'] unless element['displayStyle'].nil?
Layer.new(
name: element['name'], visible: element['visible'], is_folder: is_folder,
color: color, full_path: full_path,
layers_and_folders: element_to_relation(element['elements'], source_app, layers_tree)
)
end.compact
end
# rubocop:enable Metrics/CyclomaticComplexity
def self.to_native(obj, source_app, sketchup_model)
layers_relation = extract_relations(obj, source_app)
return if layers_relation.nil?
folder = sketchup_model.layers
is_flat = source_app.include?('rhino')
if is_flat
SpeckleObjects::Relations::Layer.to_native_flat_layers(layers_relation, sketchup_model)
else
SpeckleObjects::Relations::Layer.to_native_layer_folder(layers_relation, folder, sketchup_model)
end
active_layer = folder.to_a.find { |layer| layer.display_name == layers_relation['active_layer'] }
sketchup_model.active_layer = active_layer unless active_layer.nil?
end
def self.from_model(sketchup_model)
# init with headless layers
headless_layers = sketchup_model.layers.layers.collect do |layer|
SpeckleObjects::Relations::Layer.from_layer(layer)
end
folders = sketchup_model.layers.folders.collect do |folder|
SpeckleObjects::Relations::Layer.from_folder(folder)
end
Layers.new(
active: sketchup_model.active_layer.display_name,
layers: headless_layers + folders
)
end
end
end
end
end
@@ -1,12 +1,12 @@
# frozen_string_literal: true
require_relative '../block_definition'
require_relative '../../base'
require_relative '../other/block_definition'
require_relative '../base'
module SpeckleConnector
module SpeckleObjects
module Other
module Revit
module Revit
module Other
# RevitDefinition for Speckle.
class RevitDefinition < Base
SPECKLE_TYPE = OBJECTS_OTHER_REVIT_REVITINSTANCE
@@ -15,15 +15,17 @@ module SpeckleConnector
family = def_obj['family']
type = def_obj['type']
category = def_obj['category']
element_id = def_obj['elementId']
id = def_obj['id']
return "#{family}-#{type}-#{category}-#{def_obj['id']}"
return "#{family}-#{type}-#{category}-#{element_id}-#{id}"
end
def self.to_native(state, definition, layer, entities, &convert_to_native)
definition_name = get_definition_name(definition)
definition['name'] = definition_name
definition['displayValue'] += definition['elements'] unless definition['elements'].nil?
BlockDefinition.to_native(state, definition, layer, entities, &convert_to_native)
SpeckleObjects::Other::BlockDefinition.to_native(state, definition, layer, entities, &convert_to_native)
end
end
end
@@ -1,17 +1,12 @@
# frozen_string_literal: true
require_relative 'revit_definition'
require_relative '../render_material'
require_relative '../transform'
require_relative '../block_definition'
require_relative '../../base'
require_relative '../../geometry/bounding_box'
require_relative '../../../sketchup_model/dictionary/dictionary_handler'
require_relative '../base'
module SpeckleConnector
module SpeckleObjects
module Other
module Revit
module Revit
module Other
# RevitInstance object definition for Speckle.
class RevitInstance < Base
SPECKLE_TYPE = OBJECTS_OTHER_REVIT_REVITINSTANCE
@@ -35,8 +30,12 @@ module SpeckleConnector
definition = state.sketchup_state.sketchup_model
.definitions[RevitDefinition.get_definition_name(block_definition)]
return BlockInstance.add_instance_from_definition(state, block, layer, entities,
definition, false, &convert_to_native)
block_layer = state.sketchup_state.sketchup_model.layers.to_a
.find { |l| l.display_name == block['category'] }
return SpeckleObjects::Other::BlockInstance.add_instance_from_definition(
state, block, block_layer, layer, entities, definition, false, &convert_to_native
)
end
end
end
@@ -0,0 +1,43 @@
# frozen_string_literal: true
require_relative '../../../base'
require_relative '../../../../constants/type_constants'
module SpeckleConnector
module SpeckleObjects
module Speckle
module Core
module Models
# Collection object that collect other speckle objects under it's elements.
class Collection < Base
# @param name [String] name of the collection.
# @param collection_type [String] type of the collection like, layers, categories etc..
# @param elements [Array<Object>] elements of the collection.
# @param application_id [String, nil] id of the collection on the model.
def initialize(name:, collection_type:, elements: [], application_id: nil)
super(
speckle_type: SPECKLE_CORE_MODELS_COLLECTION,
total_children_count: 0,
application_id: application_id,
id: nil
)
self[:name] = name
self[:collectionType] = collection_type
self[:elements] = elements
end
def self.to_native(state, collection, layer, entities, &convert_to_native)
collection_type = collection['collectionType']
if collection_type.include?('model')
ModelCollection.to_native(state, collection, layer, entities, &convert_to_native)
else
LayerCollection.to_native(state, collection, layer, entities, &convert_to_native)
end
end
end
end
end
end
end
end
@@ -0,0 +1,119 @@
# frozen_string_literal: true
require_relative 'collection'
require_relative '../../../../sketchup_model/query/layer'
require_relative '../../../other/color'
require_relative '../../../other/display_style'
module SpeckleConnector
module SpeckleObjects
module Speckle
module Core
module Models
# LayerCollection object that collect other speckle objects under it's elements.
class LayerCollection < Collection
SPECKLE_TYPE = 'Speckle.Core.Models.Collection'
# rubocop:disable Metrics/ParameterLists
def initialize(name:, visible:, is_folder:, display_style: nil, color: nil, elements: [],
application_id: nil)
super(
name: name,
collection_type: 'layer',
elements: elements,
application_id: application_id
)
self[:visible] = visible
self[:is_folder] = is_folder
self[:color] = color unless color.nil?
self[:displayStyle] = display_style unless display_style.nil?
end
# rubocop:enable Metrics/ParameterLists
def self.create_layer_collections(sketchup_model)
headless_layers = sketchup_model.layers.layers.collect do |layer|
from_layer(layer)
end
folders = sketchup_model.layers.folders.collect do |folder|
from_folder(folder)
end
headless_layers + folders
end
# @param folder [Sketchup::LayerFolder] sketchup layer folder that might contains other folders and layers.
def self.from_folder(folder)
layers = folder.layers.collect { |layer| from_layer(layer) }
sub_folders = folder.folders.collect { |sub_folder| from_folder(sub_folder) }
LayerCollection.new(
name: folder.display_name,
visible: folder.visible?,
is_folder: true,
elements: layers + sub_folders,
application_id: folder.persistent_id
)
end
# @param layer [Sketchup::Layer] sketchup layer (tag) that objects can be assigned..
def self.from_layer(layer)
LayerCollection.new(
name: layer.display_name,
visible: layer.visible?,
is_folder: false,
display_style: Other::DisplayStyle.from_layer(layer),
application_id: layer.persistent_id
)
end
# @param entity_layer [Sketchup::Layer] entity layer.
# @param collection [Array] collection to search elements for entity's layer.
# rubocop:disable Metrics/CyclomaticComplexity
def self.get_or_create_layer_collection(entity_layer, collection)
folder_path = SpeckleConnector::SketchupModel::Query::Layer.path(entity_layer)
entity_layer_path = folder_path + [entity_layer]
entity_layer_path.each do |folder|
collection_candidate = collection[:elements].find do |el|
next if el.is_a?(Array)
el[:speckle_type] == SPECKLE_TYPE && el[:collectionType] == 'layer' &&
el[:name] == folder.display_name
end
if collection_candidate.nil?
color = folder.respond_to?(:color) ? SpeckleObjects::Other::Color.to_speckle(folder.color) : nil
collection_candidate = LayerCollection.new(
name: folder.display_name, visible: folder.visible?,
is_folder: folder.is_a?(Sketchup::LayerFolder), color: color
)
# Before switching collection with the new one, we should add it to current collection's elements
collection[:elements].append(collection_candidate)
end
collection = collection_candidate
end
collection
end
# rubocop:enable Metrics/CyclomaticComplexity
# @param state [States::State] state of the Speckle application.
def self.to_native(state, layer_collection, layer_or_folder, entities, &convert_to_native)
sketchup_model = state.sketchup_state.sketchup_model
elements = layer_collection['elements']
name = layer_collection['name']
name = layer_collection['full_path'] if layer_collection['full_path']
layer = sketchup_model.layers.find { |l| l.display_name == name }
layer_or_folder = layer if layer
elements.each do |element|
new_state, _converted_entities = convert_to_native.call(state, element, layer_or_folder, entities)
state = new_state
end
return state, []
end
end
end
end
end
end
end
@@ -0,0 +1,110 @@
# frozen_string_literal: true
require_relative 'collection'
require_relative 'layer_collection'
require_relative '../../../built_elements/view3d'
require_relative '../../../built_elements/revit/direct_shape'
require_relative '../../../../mapper/mapper'
module SpeckleConnector
module SpeckleObjects
module Speckle
module Core
module Models
# ModelCollection object that collect other speckle objects under it's elements.
class ModelCollection < Collection
DIRECT_SHAPE = SpeckleObjects::BuiltElements::Revit::DirectShape
QUERY = SketchupModel::Query
VIEW3D = SpeckleObjects::BuiltElements::View3d
SPECKLE_SCHEMA_DICTIONARY_HANDLER = SketchupModel::Dictionary::SpeckleSchemaDictionaryHandler
def initialize(name:, active_layer:, elements: [], application_id: nil)
super(
name: name,
collection_type: 'sketchup model',
elements: elements,
application_id: application_id
)
self[:active_layer] = active_layer
end
def self.from_sketchup_model(sketchup_model, speckle_state, units, preferences, &convert)
model_collection = ModelCollection.new(
name: 'Sketchup Model', active_layer: sketchup_model.active_layer.display_name,
application_id: sketchup_model.guid
)
# Direct shapes will pass directly to elements which are already flattened with all children
model_collection[:elements] += collect_mapped_entities(speckle_state, sketchup_model, units,
preferences, &convert)
# Views will pass directly to elements since they don't have any relation with layers and geometries.
model_collection[:elements] += VIEW3D.from_model(sketchup_model, units) if sketchup_model.pages.any?
# Add layer collections.
model_collection[:elements] += LayerCollection.create_layer_collections(sketchup_model)
sketchup_model.selection.each do |entity|
layer_collection = LayerCollection.get_or_create_layer_collection(entity.layer, model_collection)
new_speckle_state, converted_object_with_entity = convert.call(entity, preferences, speckle_state)
speckle_state = new_speckle_state
unless converted_object_with_entity.nil?
layer_collection[:elements] = [] if layer_collection[:elements].nil?
layer_collection[:elements].append(converted_object_with_entity)
end
end
return speckle_state, model_collection
end
# @param sketchup_model [Sketchup::Model] active model to retrieve and convert mapped entities.
def self.collect_mapped_entities(speckle_state, sketchup_model, units, preferences, &convert)
mapped_entities = Mapper.mapped_entities_on_selection(sketchup_model)
mapped_entities.collect do |entity_with_path|
convert_mapped_entity(speckle_state, entity_with_path, preferences, units)
end
end
def self.to_native(state, model_collection, layer, entities, &convert_to_native)
elements = model_collection['elements']
views = model_collection['@Views']
if views
views.each do |view|
new_state, _converted_entities = convert_to_native.call(state, view, layer, entities)
state = new_state
end
end
elements.each do |element|
new_state, _converted_entities = convert_to_native.call(state, element, layer, entities)
state = new_state
end
active_layer = model_collection['active_layer']
state.sketchup_state.sketchup_model.active_layer = active_layer unless active_layer.nil?
return state, []
end
def self.convert_mapped_entity(speckle_state, entity_with_path, preferences, units)
entity = entity_with_path[0]
path = entity_with_path[1..-1]
method = SPECKLE_SCHEMA_DICTIONARY_HANDLER.get_attribute(entity, 'method')
if !method.nil? && (method.include?('Floor') || method.include?('Wall')) && entity.is_a?(Sketchup::Face)
global_transformation = QUERY::Entity.global_transformation(entity, path)
floor = SpeckleObjects::Geometry::Mesh.from_face(speckle_state: speckle_state, face: entity,
units: units, model_preferences: preferences,
global_transform: global_transformation)
return [floor, [entity]]
end
direct_shape = DIRECT_SHAPE.from_entity(speckle_state, entity, path, units, preferences)
return [direct_shape, [entity]]
end
end
end
end
end
end
end
@@ -0,0 +1,45 @@
# frozen_string_literal: true
require_relative '../immutable/immutable'
require_relative '../callbacks/callback_message'
require_relative '../speckle_entities/speckle_entity'
require_relative '../mapper/mapper_source'
module SpeckleConnector
module States
# State of the speckle on ruby.
class SpeckleMapperState
include Immutable::ImmutableUtils
# @return [ImmutableHash{Integer=>Sketchup::Entity}] persistent_id of the sketchup entity and itself
attr_reader :mapped_entities
# @return [Mapper::MapperSource] source of the mapper.
attr_reader :mapper_source
def initialize
@mapped_entities = Immutable::EmptyHash
@mapper_source = nil
end
def with_mapped_entity(entity)
new_mapped_entities = mapped_entities.put(entity.persistent_id, entity)
with_mapped_entities(new_mapped_entities)
end
def with_removed_mapped_entity(entity)
new_mapped_entities = mapped_entities.delete(entity.persistent_id)
with_mapped_entities(new_mapped_entities)
end
def with_mapped_entities(new_mapped_entities)
with(:@mapped_entities => new_mapped_entities)
end
def with_mapper_source(mapper_source)
# TODO: Check/Sync here parameters of the mapped entities.
with(:@mapper_source => mapper_source)
end
end
end
end
@@ -1,5 +1,6 @@
# frozen_string_literal: true
require_relative 'speckle_mapper_state'
require_relative '../immutable/immutable'
require_relative '../callbacks/callback_message'
require_relative '../speckle_entities/speckle_entity'
@@ -10,6 +11,9 @@ module SpeckleConnector
class SpeckleState
include Immutable::ImmutableUtils
# @return [States::SpeckleMapperState] state of the mapper.
attr_reader :speckle_mapper_state
# @return [ImmutableHash{Integer=>SpeckleEntities::SpeckleEntity}] persistent_id of the sketchup entity and
# corresponding speckle entity
attr_reader :speckle_entities
@@ -30,13 +34,24 @@ module SpeckleConnector
# @return [Relations::ManyToOneRelation] relations between objects.
attr_accessor :relation
# TODO: Do cashing later
# @return [ImmutableHash{String=>SpeckleObjects::Other::RenderMaterial}] converted render materials
attr_accessor :render_materials
# TODO: Do cashing later
# @return [ImmutableHash{String=>SpeckleObjects::Other::BlockDefinition}] converted component definitions
attr_accessor :definitions
def initialize(accounts, observers, queue, stream_queue)
@accounts = accounts
@observers = observers
@message_queue = queue
@stream_queue = stream_queue
@speckle_entities = Immutable::EmptyHash
@render_materials = Immutable::EmptyHash
@definitions = Immutable::EmptyHash
@relation = Relations::ManyToOneRelation.new
@speckle_mapper_state = SpeckleMapperState.new
end
# @param callback_name [String] name of the callback command
@@ -48,6 +63,23 @@ module SpeckleConnector
with(:@message_queue => new_queue)
end
def with_mapped_entities_queue(mapped_entities)
new_queue = message_queue.merge({ "mappedEntitiesUpdated":
"mappedEntitiesUpdated(#{JSON.generate(mapped_entities)})" })
with(:@message_queue => new_queue)
end
def with_mapper_selection_queue(selection_parameters)
new_queue = message_queue.merge({ "entitySelected":
"entitySelected(#{JSON.generate(selection_parameters)})" })
with(:@message_queue => new_queue)
end
def with_mapper_deselection_queue
new_queue = message_queue.merge({ "entitiesDeselected": 'entitiesDeselected()' })
with(:@message_queue => new_queue)
end
def with_invalid_streams_queue
new_queue = message_queue.merge({ "updateInvalidStreams":
"updateInvalidStreams(#{JSON.generate(invalid_streams)})" })
@@ -64,6 +96,31 @@ module SpeckleConnector
with(:@accounts => new_accounts)
end
def with_mapper_source(mapper_source)
new_speckle_mapper_state = speckle_mapper_state.with_mapper_source(mapper_source)
with(:@speckle_mapper_state => new_speckle_mapper_state)
end
def with_removed_mapper_source
new_speckle_mapper_state = speckle_mapper_state.with_mapper_source(nil)
with(:@speckle_mapper_state => new_speckle_mapper_state)
end
def with_mapped_entity(entity)
new_speckle_mapper_state = speckle_mapper_state.with_mapped_entity(entity)
with(:@speckle_mapper_state => new_speckle_mapper_state)
end
def with_removed_mapped_entity(entity)
new_speckle_mapper_state = speckle_mapper_state.with_removed_mapped_entity(entity)
with(:@speckle_mapper_state => new_speckle_mapper_state)
end
def with_mapped_entities(new_mapped_entities)
new_speckle_mapper_state = speckle_mapper_state.with_mapped_entities(new_mapped_entities)
with(:@speckle_mapper_state => new_speckle_mapper_state)
end
def with_speckle_entity(traversed_entity)
new_speckle_entities = speckle_entities.put(traversed_entity.application_id, traversed_entity)
with_speckle_entities(new_speckle_entities)
+14
View File
@@ -36,6 +36,20 @@ module SpeckleConnector
with(:@speckle_state => new_speckle_state)
end
def with_mapped_entities_queue(mapped_entities)
new_speckle_state = speckle_state.with_mapped_entities_queue(mapped_entities)
with(:@speckle_state => new_speckle_state)
end
def with_mapper_selection_queue(selection_parameters)
new_speckle_state = if selection_parameters[:selection].any?
speckle_state.with_mapper_selection_queue(selection_parameters)
else
speckle_state.with_mapper_deselection_queue
end
with(:@speckle_state => new_speckle_state)
end
def with_empty_stream_queue
new_speckle_state = speckle_state.with(:@stream_queue => {})
with(:@speckle_state => new_speckle_state)
+23 -1
View File
@@ -14,6 +14,9 @@ require_relative '../commands/notify_connected'
require_relative '../commands/user_preferences_updated'
require_relative '../commands/model_preferences_updated'
require_relative '../commands/activate_diffing'
require_relative '../commands/apply_mappings'
require_relative '../commands/clear_mappings'
require_relative '../commands/mapper_source_updated'
require_relative '../actions/reload_accounts'
require_relative '../actions/load_saved_streams'
@@ -21,6 +24,13 @@ require_relative '../actions/init_local_accounts'
require_relative '../actions/collect_preferences'
require_relative '../actions/deactivate_diffing'
require_relative '../actions/collect_versions'
require_relative '../actions/mapped_entities_updated'
require_relative '../actions/clear_mappings_from_table'
require_relative '../actions/isolate_mappings_from_table'
require_relative '../actions/hide_mappings_from_table'
require_relative '../actions/select_mappings_from_table'
require_relative '../actions/show_all_entities'
require_relative '../actions/clear_mapper_source'
module SpeckleConnector
module Ui
@@ -56,6 +66,7 @@ module SpeckleConnector
private
# rubocop:disable Metrics/MethodLength
def commands
@commands ||= {
dialog_ready: Commands::DialogReady.new(@app),
@@ -72,9 +83,20 @@ module SpeckleConnector
user_preferences_updated: Commands::UserPreferencesUpdated.new(@app),
model_preferences_updated: Commands::ModelPreferencesUpdated.new(@app),
activate_diffing: Commands::ActivateDiffing.new(@app),
deactivate_diffing: Commands::ActionCommand.new(@app, Actions::DeactivateDiffing)
deactivate_diffing: Commands::ActionCommand.new(@app, Actions::DeactivateDiffing),
collect_mapped_entities: Commands::ActionCommand.new(@app, Actions::MappedEntitiesUpdated),
apply_mappings: Commands::ApplyMappings.new(@app),
clear_mappings: Commands::ClearMappings.new(@app),
clear_mappings_from_table: Commands::ActionCommand.new(@app, Actions::ClearMappingsFromTable),
isolate_mappings_from_table: Commands::ActionCommand.new(@app, Actions::IsolateMappingsFromTable),
hide_mappings_from_table: Commands::ActionCommand.new(@app, Actions::HideMappingsFromTable),
select_mappings_from_table: Commands::ActionCommand.new(@app, Actions::SelectMappingsFromTable),
show_all_entities: Commands::ActionCommand.new(@app, Actions::ShowAllEntities),
mapper_source_updated: Commands::MapperSourceUpdated.new(@app),
clear_mapper_source: Commands::ActionCommand.new(@app, Actions::ClearMapperSource)
}.freeze
end
# rubocop:enable Metrics/MethodLength
end
end
end
+14 -14
View File
@@ -14,13 +14,13 @@
"mixpanel-browser": "^2.45.0",
"regenerator-runtime": "^0.13.9",
"register-service-worker": "^1.7.1",
"sqlite3": "^5.0.2",
"sqlite3": "^5.1.5",
"v-tooltip": "^2.1.3",
"vue": "^2.6.11",
"vue-apollo": "^3.0.0-beta.11",
"vue-router": "^3.2.0",
"vue-timeago": "^5.1.3",
"vuetify": "^2.4.0"
"vuetify": "2.6.10"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
@@ -22707,9 +22707,9 @@
"dev": true
},
"node_modules/sqlite3": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.0.8.tgz",
"integrity": "sha512-f2ACsbSyb2D1qFFcqIXPfFscLtPVOWJr5GmUzYxf4W+0qelu5MWrR+FAQE1d5IUArEltBrzSDxDORG8P/IkqyQ==",
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.5.tgz",
"integrity": "sha512-7sP16i4wI+yKnGOO2q2ijze7EjQ9US+Vw7DYYwxfFtqNZDGgBcEw0oeDaDvUTq66uJOzVd/z6MkIg+c9erSJKg==",
"hasInstallScript": true,
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.0",
@@ -25264,9 +25264,9 @@
}
},
"node_modules/vuetify": {
"version": "2.6.6",
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.6.tgz",
"integrity": "sha512-H4KtxDFmDN8QiTRiGfBySyjMhVaHAJTKB0llGGKZT5jKxtnx9gvEtMWXKtVuRP0NJJP0H6xBPJHNOH7nT18qiQ==",
"version": "2.6.10",
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.10.tgz",
"integrity": "sha512-fgUeRDDCwYkwu6WGEEKGe7IdfzOsXJCZGrqkn1pcS2ycuoDL8mR2/dejH5iFNnBY6MnsT365PAGn0J+9otjfQg==",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/johnleider"
@@ -44593,9 +44593,9 @@
"dev": true
},
"sqlite3": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.0.8.tgz",
"integrity": "sha512-f2ACsbSyb2D1qFFcqIXPfFscLtPVOWJr5GmUzYxf4W+0qelu5MWrR+FAQE1d5IUArEltBrzSDxDORG8P/IkqyQ==",
"version": "5.1.5",
"resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.5.tgz",
"integrity": "sha512-7sP16i4wI+yKnGOO2q2ijze7EjQ9US+Vw7DYYwxfFtqNZDGgBcEw0oeDaDvUTq66uJOzVd/z6MkIg+c9erSJKg==",
"requires": {
"@mapbox/node-pre-gyp": "^1.0.0",
"node-addon-api": "^4.2.0",
@@ -46606,9 +46606,9 @@
}
},
"vuetify": {
"version": "2.6.6",
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.6.tgz",
"integrity": "sha512-H4KtxDFmDN8QiTRiGfBySyjMhVaHAJTKB0llGGKZT5jKxtnx9gvEtMWXKtVuRP0NJJP0H6xBPJHNOH7nT18qiQ==",
"version": "2.6.10",
"resolved": "https://registry.npmjs.org/vuetify/-/vuetify-2.6.10.tgz",
"integrity": "sha512-fgUeRDDCwYkwu6WGEEKGe7IdfzOsXJCZGrqkn1pcS2ycuoDL8mR2/dejH5iFNnBY6MnsT365PAGn0J+9otjfQg==",
"requires": {}
},
"vuetify-loader": {
+2 -2
View File
@@ -17,13 +17,13 @@
"mixpanel-browser": "^2.45.0",
"regenerator-runtime": "^0.13.9",
"register-service-worker": "^1.7.1",
"sqlite3": "^5.0.2",
"sqlite3": "^5.1.5",
"v-tooltip": "^2.1.3",
"vue": "^2.6.11",
"vue-apollo": "^3.0.0-beta.11",
"vue-router": "^3.2.0",
"vue-timeago": "^5.1.3",
"vuetify": "^2.4.0"
"vuetify": "2.6.10"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
+76 -32
View File
@@ -1,25 +1,26 @@
<template>
<v-app>
<v-main>
<v-app-bar app flat>
<v-app-bar app flat height="50">
<v-img
class="mx-auto"
class="mx-auto px-0"
max-width="45"
src="@/assets/logo.svg"
style="display: inline-block"
/>
<v-text-field
v-model="streamSearchQuery"
prepend-inner-icon="mdi-magnify"
label="Search streams"
hide-details
clearable
rounded
filled
dense
flat
solo
/>
<v-tabs
v-model="tab"
align-tabs="title"
class="mx-sm-1"
>
<v-tabs-slider class="mx-sm-1"></v-tabs-slider>
<v-tab href="#streams">
{{ streamsText }}
</v-tab>
<v-tab href="#mapper">
{{"Mapper"}}
</v-tab>
</v-tabs>
<v-spacer />
<v-btn icon small class="mx-1" @click="requestRefresh">
<v-icon>mdi-refresh</v-icon>
@@ -80,19 +81,45 @@
</v-menu>
</v-app-bar>
<create-stream-dialog
v-if="accounts().length !== 0"
:account-id="activeAccount().userInfo.id"
:server-url="activeAccount().serverInfo.url"
/>
<v-container v-if="accounts().length !== 0" fluid>
<router-view :stream-search-query="streamSearchQuery" />
</v-container>
<v-container v-else>
<login/>
</v-container>
<global-toast />
<v-tabs-items v-model="tab">
<v-tab-item :key="1" value="streams">
<v-container class="ma-0 pa-0">
<v-container>
<v-text-field
v-model="streamSearchQuery"
prepend-inner-icon="mdi-magnify"
:label="searchText"
background-color="background"
hide-details
clearable
rounded
filled
dense
flat
solo
/>
</v-container>
<create-stream-dialog
v-if="accounts().length !== 0"
:is-f-e2="preferences && preferences.user && preferences.user.fe2"
:account-id="activeAccount().userInfo.id"
:server-url="activeAccount().serverInfo.url"
/>
<v-container v-if="accounts().length !== 0" fluid>
<router-view :stream-search-query="streamSearchQuery" />
</v-container>
<v-container v-else>
<login/>
</v-container>
<global-toast />
</v-container>
</v-tab-item>
<v-tab-item :key="2" value="mapper">
<v-card flat>
<mapper :stream-text="streamText" :branch-text="branchText"></mapper>
</v-card>
</v-tab-item>
</v-tabs-items>
</v-main>
</v-app>
</template>
@@ -141,13 +168,14 @@ export default {
Login,
CreateStreamDialog: () => import('@/components/dialogs/CreateStreamDialog'),
SettingsDialog: () => import('@/components/dialogs/SettingsDialog'),
GlobalToast: () => import('@/components/GlobalToast')
GlobalToast: () => import('@/components/GlobalToast'),
Mapper: () => import('@/components/Mapper')
},
props: {
size: {
type: Number,
default: 42
},
}
},
data() {
return {
@@ -155,7 +183,12 @@ export default {
createNewStreamDialog: false,
createStreamByIdDialog: false,
createStreamByIdText: "",
preferences: {}
preferences: {},
tab: "streams",
searchText: '',
streamsText: 'Streams',
streamText: 'Stream',
branchText: 'Branch'
}
},
computed: {
@@ -181,8 +214,11 @@ export default {
})
bus.$on('update-preferences', async (preferences) => {
let prefs = JSON.parse(preferences)
this.preferences = prefs
this.preferences = JSON.parse(preferences)
this.searchText = this.preferences.user.fe2 ? 'Search projects' : 'Search streams'
this.streamsText = this.preferences.user.fe2 ? 'Projects' : 'Streams'
this.streamText = this.preferences.user.fe2 ? 'Project' : 'Stream'
this.branchText = this.preferences.user.fe2 ? 'Model' : 'Branch'
this.$vuetify.theme.dark = this.preferences.user.dark_theme
})
@@ -219,3 +255,11 @@ export default {
}
}
</script>
<style>
/deep/ .v-toolbar__content {
padding: 0px !important;
}
</style>
+7
View File
@@ -34,6 +34,13 @@ export default {
}
},
mounted() {
this.$eventHub.$on('success', (args) => {
this.snack = true
this.color = "green"
this.text = args.text
this.actionName = args.action ? args.action.name : null
this.url = args.action ? args.action.url : null
})
this.$eventHub.$on('notification', (args) => {
this.snack = true
this.color = "primary"
+313
View File
@@ -0,0 +1,313 @@
<template>
<v-container class="pa-0">
<v-data-table
v-model="categorySelection"
class="elevation-1 mb-2"
dense
expand
disable-filtering
disable-pagination
hide-default-footer
hide-default-header
item-key="categoryName"
:expanded.sync="mappedElementsExpandedIndexes"
:headers="mappedElementsHeaders"
:items="mappedEntitiesTableData"
:mobile-breakpoint="0"
show-select
>
<template #header="{ props: { headers } }">
<thead class="v-data-table-header">
<tr>
<th
v-for="header in headers"
:key="header.value"
:width="header.width"
scope="col"
class="text-center"
>
{{ header.text }}
</th>
</tr>
</thead>
</template>
<template v-slot:expanded-item="{ headers, item }">
<td :colspan="headers.length" class="pl-2 pr-0">
<v-data-table
v-model="elementSelection[item.categoryName]['selectedElements']"
class="elevation-0 pa-0 ma-0 mb-2"
dense
disable-filtering
disable-pagination
hide-default-footer
hide-default-header
item-key="entityId"
:headers="subMappedElementsHeaders"
:items="elementSelection[item.categoryName]['allElements']"
:mobile-breakpoint="0"
show-select
>
<template #header="{ props: { headers } }">
<thead class="v-data-table-header">
<tr>
<th
v-for="header in headers"
:key="header.value"
:width="header.width"
scope="col"
class="text-center"
>
{{ header.text }}
</th>
</tr>
</thead>
</template>
<template #[`item.data-table-select`]="slotData">
<td class="mapped-elements-check-box-row">
<v-checkbox
class="shrink ma-0"
hide-details
:input-value="slotData.isSelected"
@click="slotData.select(clickMappedElements(slotData, item.categoryName))"
/>
</td>
</template>
</v-data-table>
</td>
</template>
<template #[`header.name`]="{ header }">
<th class="header-text-color">{{ header.text.toUpperCase() }}</th>
</template>
<template #[`item.categoryName`]="slotData">
<div @click="clickMappedElementsCategory(slotData)">{{ slotData.item.categoryName }}</div>
</template>
<template #[`item.data-table-select`]="slotData">
<v-checkbox
class="shrink ma-0"
hide-details
:input-value="slotData.isSelected"
@click="slotData.select(clickMappedCategory(slotData))"
/>
</template>
</v-data-table>
<v-container class="btn-container px-0">
<v-btn
v-tooltip="'Clear Mappings'"
x-small
min-width="30px"
min-height="30px"
@click="clearMappingsFromTableSelection"
>
<v-icon left dark>
mdi-delete
</v-icon>
Clear
</v-btn>
<v-spacer></v-spacer>
<v-btn
v-tooltip=" isIsolated ? 'Show All Elements' : 'Isolate Mapped Elements'"
x-small
min-width="30px"
min-height="30px"
class="mr-2"
@click="isolateMappedElementsOnSketchup"
>
<v-icon left dark>
{{ isIsolated ? 'mdi-crop-rotate' : 'mdi-crop'}}
</v-icon>
{{ isIsolated ? 'Show All' : 'Isolate'}}
</v-btn>
<v-btn
v-tooltip="'Hide Mapped Elements'"
x-small
min-width="30px"
min-height="30px"
class="mr-2"
@click="hideMappedElementsOnSketchup"
>
<v-icon left dark>
mdi-eye-off
</v-icon>
Hide
</v-btn>
<v-btn
v-tooltip="'Select Mapped Elements'"
x-small
min-width="30px"
min-height="30px"
@click="selectMappedElementsOnSketchup"
>
<v-icon left dark>
mdi-select-all
</v-icon>
Select
</v-btn>
</v-container>
</v-container>
</template>
<script>
/*global sketchup*/
import {bus} from "@/main";
import {groupBy} from "@/utils/groupBy";
export default {
name: "MappedElements",
data(){
return {
isIsolated: false,
elementSelection: {},
categorySelection: [],
mappedEntities: [],
mappedEntityCount: 0,
// Expanded indexes for mapped element table (Categories)
mappedElementsExpandedIndexes: [],
mappedElementsHeaders: [
{ text: 'Category', sortable: false, align: 'center', value: 'categoryName', width: '70%' },
{ text: 'Count', sortable: false, align: 'center', value: 'count', width: '30%' }
],
subMappedElementsHeaders: [
{ text: 'Type', sortable: false, align: 'center', value: 'entityType', width: '70%' },
{ text: 'Name/Id', sortable: false, align: 'center', value: 'nameOrId', width: '30%' },
],
mappedEntitiesTableData: [],
}
},
computed: {
// categorySelection() {
// return Object.keys(this.elementSelection)
// },
},
mounted() {
sketchup.exec({name: "collect_mapped_entities", data: {}})
bus.$on('mapped-entities-updated', async (mappedEntities) => {
this.mappedEntityCount = mappedEntities.length
this.mappedEntities = mappedEntities
this.getMappedElementsTableData()
})
},
methods: {
// categorySelection() {
// return Object.keys(this.elementSelection)
// },
clickMappedElementsCategory(slotData) {
const indexExpanded = this.mappedElementsExpandedIndexes.findIndex(i => i === slotData.item);
if (indexExpanded > -1) {
this.mappedElementsExpandedIndexes.splice(indexExpanded, 1)
} else {
this.mappedElementsExpandedIndexes.push(slotData.item);
}
},
clickMappedCategory(slotData){
const category = this.elementSelection[slotData.item.categoryName]
if (category['allSelected'] || category['selectedElements'].length === category['entityCount']) {
this.elementSelection[slotData.item.categoryName]['allSelected'] = false
this.elementSelection[slotData.item.categoryName]['selectedElements'] = []
} else {
this.elementSelection[slotData.item.categoryName]['allSelected'] = true
this.elementSelection[slotData.item.categoryName]['selectedElements'] = slotData.item.entities
}
},
clickMappedElements(slotData, category){
const elements = this.elementSelection[category]['selectedElements'] === undefined ? [] : this.elementSelection[category]['selectedElements']
const indexSelection = elements.findIndex(i => i['entityId'] === slotData.item['entityId']);
if (indexSelection > -1) {
elements.splice(indexSelection, 1)
} else {
elements.push(slotData.item);
}
this.elementSelection[category]['selectedElements'] = elements
// FIXME: This should be the ideal UX, but there is a problem with states currently.. Need to be fixed
// if (elements.length === 0){
// this.elementSelection[category]['allSelected'] = false
// }
},
clearMappingsFromTableSelection(){
sketchup.exec({ name: "clear_mappings_from_table", data: this.elementSelection })
this.$mixpanel.track('MappingsAction', { name: 'Mappings Clear' })
},
isolateMappedElementsOnSketchup(){
if (this.isIsolated){
this.isIsolated = false
sketchup.exec({ name: "show_all_entities", data: {} })
this.$mixpanel.track('MappingsAction', { name: 'Mappings Un-Isolate' })
} else {
this.isIsolated = true
sketchup.exec({ name: "isolate_mappings_from_table", data: this.elementSelection })
this.$mixpanel.track('MappingsAction', { name: 'Mappings Isolate' })
}
},
hideMappedElementsOnSketchup(){
sketchup.exec({ name: "hide_mappings_from_table", data: this.elementSelection })
this.$mixpanel.track('MappingsAction', { name: 'Mappings Hide' })
},
selectMappedElementsOnSketchup(){
sketchup.exec({ name: "select_mappings_from_table", data: this.elementSelection })
this.$mixpanel.track('MappingsAction', { name: 'Mappings Select Elements' })
},
// Update mapped elements table whenever mapped elements has changed.
getMappedElementsTableData(){
let groupByCategoryName = groupBy('categoryName')
let groupedByCategoryName = groupByCategoryName(this.mappedEntities)
// Reset selected categories and elements whenever mapped elements states has changed
this.elementSelection = {}
this.categorySelection = []
this.mappedElementsExpandedIndexes = []
this.mappedEntitiesTableData = Object.entries(groupedByCategoryName).map(
(entry) => {
const [categoryName, entities] = entry
this.elementSelection[categoryName] = {
allSelected: false,
entityCount: entities.length,
selectedElements: [],
allElements: entities.map((entity) => {
return {
'entityId': entity['entityId'],
'nameOrId': entity['name'] !== "" ? entity['name'] : entity['entityId'],
'entityType': entity['entityType']
}
})
}
return {
'categoryName': categoryName,
'count': entities !== true ? entities.length : 0,
'entities': entities.map((entity) => {
return {
'entityId': entity['entityId'],
'nameOrId': entity['name'] !== "" ? entity['name'] : entity['entityId'],
'entityType': entity['entityType']
}
})
}
}
)
},
}
}
</script>
<style scoped>
.btn-container{
display: flex;
flex-wrap: wrap;
}
.mapped-elements-check-box-row {
width: 20px;
}
/* This is the header styles of child table */
.v-data-table--dense > .v-data-table__wrapper > table > thead > tr > th {
height: 32px;
}
</style>
+899
View File
@@ -0,0 +1,899 @@
<template>
<v-container fluid class="px-3 btn-container">
<v-expansion-panels
v-model="panel"
accordion
multiple
>
<v-expansion-panel key="selection">
<v-expansion-panel-header>
<div>
<v-icon>
{{ selectedEntityCount === 0 ? 'mdi-playlist-remove' : 'mdi-playlist-check' }}
</v-icon>
{{ `Selection (${selectedEntityCount})` }}
</div>
</v-expansion-panel-header>
<v-expansion-panel-content class="mx-n3">
<v-data-table
class="elevation-1"
dense
expand
disable-filtering
disable-pagination
hide-default-footer
item-key="entityType"
:expanded.sync="selectionExpandedIndexes"
:headers="selectionHeaders"
:items="selectionTableData"
:mobile-breakpoint="0"
>
<template v-slot:expanded-item="{ headers, item }">
<td :colspan="headers.length" class="pl-2 pr-0">
<v-data-table
class="elevation-0 pa-0 ma-0"
dense
disable-filtering
disable-pagination
hide-default-footer
item-key="entityId"
:headers="subSelectionHeaders"
:items="item.entities"
:mobile-breakpoint="0"
>
<template v-slot:item.isMapped="{ item }">
<v-icon :color="item.isMapped ? 'green' : 'red'">
{{ item.isMapped ? 'mdi-check-circle' : 'mdi-close-circle' }}
</v-icon>
</template>
</v-data-table>
</td>
</template>
<template v-slot:item.entityType="slotData">
<div @click="clickSelectionColumn(slotData)">{{ slotData.item.entityType }}</div>
</template>
</v-data-table>
</v-expansion-panel-content>
</v-expansion-panel>
<v-expansion-panel key="source">
<v-expansion-panel-header class="flex">
<v-container class="ma-0 pa-0">
<v-icon>
mdi-source-branch
</v-icon>
{{ `Source` }}
<v-tooltip right>
<template #activator="{ on, attrs }">
<v-btn
class="ma-0 ml-1"
height="20px"
width="20px"
icon
x-small
:color="getSourceStateIconColor()"
v-bind="attrs"
v-on="on"
@click="refreshSourceBranch"
>
<v-icon>
{{ getSourceStateIcon() }}
</v-icon>
</v-btn>
</template>
<span>{{ getSourceStateToolTip() }}</span>
</v-tooltip>
</v-container>
</v-expansion-panel-header>
<v-expansion-panel-content>
<mapper-source :stream-text="streamText" :branch-text="branchText" :source-state="this.sourceState"/>
</v-expansion-panel-content>
</v-expansion-panel>
<v-expansion-panel key="mapping">
<v-expansion-panel-header>
<div>
<v-icon>
mdi-multiplication
</v-icon>
{{ `Mapping` }}
</div>
</v-expansion-panel-header>
<v-expansion-panel-content>
<v-container v-if="entitySelected" class="btn-container pa-0 mb-5">
<v-card
variant="outlined"
class="pt-0 pl-2 mb-1 mr-2 flex"
:elevation="entityCardElevation"
:outlined="!definitionSelected"
:width="entityCardWidth"
min-width="150px"
@click="definitionSelectedHandler(false)"
>
<v-card-title class="pa-0 pb-2">
<v-icon class="mr-1 v-bottom-navigation--absolute">
{{ getSelectedEntityIcon }}
</v-icon>
{{getSelectedEntityText}}
<v-spacer></v-spacer>
<v-icon v-if="entityMapped" class="mr-n2 mt-n6" color="green">
mdi-checkbox-marked-circle
</v-icon>
</v-card-title>
<v-card-subtitle class="pb-0 pr-0 font-weight-light">
{{getSelectedEntitySubText}}
</v-card-subtitle>
</v-card>
<v-card
v-if=selectedEntitiesHasParent
variant="outlined"
class="pt-0 pl-2 mb-1 mr-2 flex"
:elevation="definitionSelected ? '6' : '1'"
:outlined="definitionSelected"
:width="entityCardWidth"
min-width="150px"
@click="definitionSelectedHandler(true)"
>
<v-card-title class="pa-0 pb-2">
<v-icon class="mr-1">
mdi-atom
</v-icon>
{{ getSelectedDefinitionText }}
<v-spacer></v-spacer>
<v-icon v-if="definitionMapped" class="mr-n2 mt-n6" color="green">
mdi-checkbox-marked-circle
</v-icon>
</v-card-title>
<v-card-subtitle class="pb-0 pr-0 font-weight-light">
{{getSelectedDefinitionSubText}}
</v-card-subtitle>
</v-card>
</v-container>
<v-autocomplete
v-model="selectedMethod"
class="pt-0"
label="Mapper Method"
:disabled="!entitySelected"
:items="availableMethods"
density="compact"
clearable
@change="onSelectedMethodChange"
></v-autocomplete>
<v-autocomplete
v-if="familySelectionActive"
v-model="selectedFamily"
class="pt-0"
label="Family"
:disabled="!entitySelected"
:items="families"
density="compact"
clearable
@change="onSelectedFamilyChange"
></v-autocomplete>
<v-autocomplete
v-if="typeSelectionActive"
v-model="selectedFamilyType"
class="pt-0"
label="Type"
:disabled="!entitySelected"
item-value="type"
item-text="type"
:items="familyTypes"
density="compact"
clearable
></v-autocomplete>
<v-autocomplete
v-if="levelSelectionActive"
v-model="selectedLevel"
class="pt-0"
label="Base Level"
:disabled="!entitySelected"
:items="levels"
item-value="name"
item-text="name"
density="compact"
clearable
></v-autocomplete>
<v-autocomplete
v-if="categorySelectionActive"
v-model="selectedCategory"
class="pt-0"
label="Category"
:items="availableCategories"
item-value="value"
item-text="key"
:disabled="!entitySelected"
density="compact"
clearable
></v-autocomplete>
<v-text-field
v-if="nameSelectionActive"
v-model="name"
class="pt-0"
label="Name"
:disabled="!entitySelected"
clearable
></v-text-field>
<v-container class="pa-0">
<v-row justify="center" align="center">
<v-col cols="auto" class="pa-1 pb-2">
<v-btn
small
:disabled="!entitySelected"
@click="applyMapping"
>
<v-icon dark left>
mdi-checkbox-marked-circle
</v-icon>Apply
</v-btn>
</v-col>
<v-col cols="auto" class="pa-1 pb-2">
<v-btn
small
:disabled="!entitySelected"
@click="clearMapping"
>
<v-icon dark left>
mdi-close-circle
</v-icon>Clear
</v-btn>
</v-col>
</v-row>
</v-container>
</v-expansion-panel-content>
</v-expansion-panel>
<v-expansion-panel key="mappedElements">
<v-expansion-panel-header>
<div>
<v-icon>
{{ mappedEntityCount === 0 ? 'mdi-playlist-remove' : 'mdi-playlist-check' }}
</v-icon>
{{ `Mapped Elements (${mappedEntityCount})` }}
</div>
</v-expansion-panel-header>
<v-expansion-panel-content class="mx-n3">
<mapped-elements/>
</v-expansion-panel-content>
</v-expansion-panel>
</v-expansion-panels>
<global-toast />
</v-container>
</template>
<script>
/*global sketchup*/
import {bus} from "@/main";
import {groupBy} from "@/utils/groupBy";
import MappingSource from "@/components/MapperSource.vue";
import {sourceMap} from "@vue/cli-service/lib/config/terserOptions";
global.mapperSourceUpdated = function (streamId, levels, types) {
console.log(`Mapper source updated for ${streamId}.`)
}
global.entitySelected = function (selectionParameters) {
bus.$emit('entities-selected', JSON.stringify(selectionParameters))
}
global.entitiesDeselected = function () {
bus.$emit('entities-deselected')
}
global.mappedEntitiesUpdated = function (mappedEntities) {
bus.$emit('mapped-entities-updated', mappedEntities)
}
export default {
name: "Mapper",
props: {
streamText: {
type: String,
default: ''
},
branchText: {
type: String,
default: ''
}
},
components: {
MapperSource: () => import('@/components/MapperSource.vue'),
GlobalToast: () => import('@/components/GlobalToast'),
MappedElements: () => import('@/components/MappedElements.vue')
},
data() {
return {
nativeFaceMethods: ['Floor', 'Wall'],
nativeEdgeMethods: ['Column', 'Beam', 'Brace', 'Pipe', 'Duct'],
nativeDefaultFaceMethods: ['Default Floor', 'Default Wall'],
nativeDefaultEdgeMethods: ['Default Column', 'Default Beam', 'Default Brace', 'Default Pipe', 'Default Duct'],
familySelectionActive: false,
typeSelectionActive: false,
levelSelectionActive: false,
categorySelectionActive: false,
nameSelectionActive: false,
sourceState: 'Not Set',
// Expanded indexes for selection table (Types)
selectionExpandedIndexes: [],
// Expanded indexes for mapped element table (Categories)
mappedElementsExpandedIndexes: [],
// Whether definition card is selected to map or not.
definitionSelected: false,
// Initial entity (Group, Component, Face, Edge) that mapped or not
entityMapped: false,
// Definition of entity is mapped, it will be available for only Components.
definitionMapped: false,
// Whether an entity is selected or not.
entitySelected: false,
selectedEntityCount: 0,
selectedEntities: [],
allFamilyTypes: {},
familyTypes: [],
lastSelectedEntity: null,
selectedMethod: null,
selectedCategory: null,
selectedFamily: null,
selectedFamilyType: null,
selectedLevel: null,
name: "",
availableMethods: [],
availableCategories: [],
families: [],
allTypes: {},
levels: [],
mappedEntityCount: 0,
mappedEntities: [],
panel: [2],
selectionHeaders: [
{ text: 'Type', sortable: false, value: 'entityType', width: '60%' },
{ text: 'Count', sortable: false, align: 'center', value: 'count', width: '20%' },
{ text: 'Mapped', sortable: false, align: 'center', value: 'mappedCount', width: '20%' },
],
subSelectionHeaders: [
{ text: 'Name/Id', sortable: false, value: 'nameOrId', width: '80%' },
{ text: 'Mapped', sortable: false, align: 'center', value: 'isMapped', width: '20%' },
],
mappedElementsHeaders: [
{ text: 'Category', sortable: false, value: 'categoryName', width: '80%' },
{ text: 'Count', sortable: false, align: 'center', value: 'count', width: '20%' }
],
subMappedElementsHeaders: [
{ text: 'Type', sortable: false, value: 'entityType', width: '80%' },
{ text: 'Name/Id', sortable: false, align: 'center', value: 'nameOrId', width: '20%' },
],
selectionTableData: [],
mappedEntitiesTableData: [],
}
},
computed:{
lastSelectedEntityHasParent(){
return this.lastSelectedEntity['entityType'] === 'Component'
},
selectedEntitiesHasParent(){
return this.selectedEntities.every((entity) => entity['entityType'] === 'Component')
},
entityCardWidth(){
if (this.lastSelectedEntityHasParent){
return '150px'
} else {
return '310px'
}
},
entityCardElevation(){
if (!this.lastSelectedEntityHasParent){
return '1'
}
return this.definitionSelected ? '1' : '6'
},
entityCardColor(){
if (!this.lastSelectedEntityHasParent){
return 'background2'
}
return this.definitionSelected ? 'background2' : 'mappingEntity'
},
getSelectedEntityIcon(){
if (this.selectedEntities.length > 1){
return 'mdi-webpack'
}else{
const type = this.lastSelectedEntity['entityType']
if (type === 'Face'){
return 'mdi-vector-square'
} else if (type === 'Edge'){
return 'mdi-vector-polyline'
} else if (type === 'Group'){
return 'mdi-border-outside'
} else if (type === 'Component'){
return 'mdi-border-inside'
} else {
return 'mdi-close'
}
}
},
getSelectedEntityText(){
if (this.selectedEntities.length > 1){
if (this.selectedEntitiesHasParent){
return 'Component'
}
return 'Multiple Selection'
}else{
return this.lastSelectedEntity["entityType"]
}
},
getSelectedDefinitionText(){
if (this.selectedEntities.length > 1 && this.selectedEntitiesHasParent){
return 'Definition'
}else{
return 'Definition'
}
},
getSelectedEntitySubText(){
if (this.selectedEntities.length > 1){
return this.getSelectionSummary()
}else{
return 'Single selected entity'
}
},
getSelectedDefinitionSubText(){
if (this.selectedEntities.length > 1){
let instances = 0
let registeredDefinitions = []
this.selectedEntities.forEach((entity) => {
if (!registeredDefinitions.includes(entity['definition']['entityId'])){
instances += entity['definition']['numberOfInstances']
registeredDefinitions.push(entity['definition']['entityId'])
}
})
return `Instances (${instances})`
}else{
return `Instances (${this.lastSelectedEntity['definition']['numberOfInstances']})`
}
}
},
methods:{
getSourceStateIcon(){
switch (this.sourceState){
case "Not Set":
return `mdi-cloud-off-outline`;
case "Set":
return `mdi-checkbox-marked-circle-outline`;
case "Outdated":
return `mdi-update`;
default:
break;
}
},
getSourceStateToolTip(){
switch (this.sourceState){
case "Not Set":
return 'Source disconnected.';
case "Set":
return 'Source connected.';
case "Outdated":
return 'Source branch is not up-to-date!';
default:
break;
}
},
getSourceStateIconColor(){
switch (this.sourceState){
case "Not Set":
return `grey`;
case "Set":
return `green`;
case "Outdated":
return `red`;
default:
break;
}
},
onSelectedMethodChange(){
this.hideOptionalMappingInputs()
this.updateMappingInputs()
this.getFamiliesFromSelectedMethod()
this.getTypesFromSelectedFamily()
this.$mixpanel.track('MappingsAction', { name: 'Mappings Set', schema: this.selectedMethod })
},
onSelectedFamilyChange(){
this.getTypesFromSelectedFamily();
},
updateMappingInputs(){
if (this.selectedMethod === null){
this.typeSelectionActive = false
this.familySelectionActive = false
this.levelSelectionActive = false
this.categorySelectionActive = false
this.nameSelectionActive = false
return
}
const nativeDefaultMethods = this.nativeDefaultEdgeMethods.concat(this.nativeDefaultFaceMethods)
const nativeMethods = this.nativeEdgeMethods.concat(this.nativeFaceMethods)
if (this.selectedMethod === 'Direct Shape'){
this.categorySelectionActive = true
this.nameSelectionActive = true
}
else if (nativeDefaultMethods.includes(this.selectedMethod)){
this.typeSelectionActive = false
this.familySelectionActive = false
this.levelSelectionActive = false
this.categorySelectionActive = false
this.nameSelectionActive = false
}
else if (nativeMethods.includes(this.selectedMethod)){
this.typeSelectionActive = true
this.familySelectionActive = true
this.levelSelectionActive = true
}
},
getTypesFromSelectedFamily(){
this.familyTypes = this.allFamilyTypes[this.selectedFamily]
this.selectedFamilyType = this.familyTypes[0].type
if (this.selectedFamily === null || this.selectedFamily === undefined){
this.selectedFamily = this.families[0]
}
if (this.familyTypes === null ||this.familyTypes === undefined){
this.familyTypes = this.allFamilyTypes[this.selectedFamily]
}
if (this.selectedFamilyType === null || this.selectedFamilyType === undefined){
this.selectedFamilyType = this.familyTypes[0].type
}
},
getFamiliesFromSelectedMethod(){
switch (this.selectedMethod) {
case 'Floor':
this.families = Object.keys(this.allTypes['Floors']);
this.allFamilyTypes = this.allTypes['Floors']
break;
case 'Wall':
this.families = Object.keys(this.allTypes['Walls']);
this.allFamilyTypes = this.allTypes['Walls']
break;
case 'Column':
this.families = Object.keys(this.allTypes['Columns']);
this.allFamilyTypes = this.allTypes['Columns']
break;
case 'Beam':
this.families = Object.keys(this.allTypes['Beams']);
this.allFamilyTypes = this.allTypes['Beams']
break;
case 'Pipe':
this.families = Object.keys(this.allTypes['Piping System']);
this.allFamilyTypes = this.allTypes['Piping System']
break;
case 'Duct':
this.families = Object.keys(this.allTypes['Duct System']);
this.allFamilyTypes = this.allTypes['Duct System']
break;
default:
break;
}
if (this.selectedFamily === null || this.selectedFamily === undefined){
this.selectedFamily = this.families[0]
}
if (this.selectedLevel === null || this.selectedLevel === undefined){
this.selectedLevel = this.levels[0].name
}
},
hideOptionalMappingInputs(){
this.categorySelectionActive = false
this.nameSelectionActive = false
this.typeSelectionActive = false
this.familySelectionActive = false
this.levelSelectionActive = false
},
refreshSourceBranch(){
if (this.sourceState === 'Outdated'){
bus.$emit('refresh-source-branch')
this.$mixpanel.track('MappingsAction', { name: 'Mappings Source Update' })
}
},
clearInputs(){
this.availableMethods = []
this.availableCategories = []
this.selectedEntities = []
this.selectionTableData = []
this.selectedEntityCount = 0
this.name = ""
this.selectedMethod = null
this.selectedCategory = null
this.entityMapped = false
this.definitionMapped = false
this.selectedLevel = null
this.selectedFamily = null
this.selectedFamilyType = null
this.allTypes = null
this.familyTypes = null
},
getSelectionTableData(){
let groupByClass = groupBy('entityType')
let groupedByWithKey = groupByClass(this.selectedEntities)
this.selectionTableData = Object.entries(groupedByWithKey).map(
(entry) => {
const [className, entities] = entry
return {
'entityType': className,
'entityIds': entities.map(entity => entity['entityId']),
'count': entities !== true ? entities.length : 0,
'entities': entities.map((entity) => {
return {
'entityId': entity['entityId'],
'nameOrId': entity['name'] !== null ? entity['name'] : entity['entityId'],
'isMapped': this.isEntityMapped(entity) || this.isEntityDefinitionMapped(entity)
}
}),
'mappedCount': entities.filter((entity) => entity['schema']['category'] !== undefined).length
}
}
)
},
getSelectionSummary(){
let groupByClass = groupBy('entityType')
let groupedByWithKey = groupByClass(this.selectedEntities)
let summary = ''
Object.entries(groupedByWithKey).forEach((entry, index) => {
const [className, entities] = entry
const entityType = className === 'Component' ? 'Instance' : className
summary += `${entityType}s (${entities.length})`
if (index !== Object.entries(groupedByWithKey).length - 1){
summary += ' - '
}
})
return summary
},
setInputValuesFromSelection(){
// Clear all inputs if entity is not selected.
if (!this.entitySelected){
this.clearMappingInputs()
return
}
// Check if definition card is selected and set definition mappings.
if (this.definitionSelected) {
if (!this.definitionMapped){
if (this.selectedEntityCount > 1){
this.name = '<Mixed>'
}else{
this.name = this.lastSelectedEntity['definition']['entityName']
}
// this.selectedMethod = 'Direct Shape'
this.selectedCategory = 49
} else {
if (this.selectedEntityCount > 1){
this.name = '<Mixed>'
}else{
this.name = this.lastSelectedEntity['definition']['schema']['name']
}
this.selectedMethod = this.lastSelectedEntity['definition']['schema']['method']
this.selectedCategory = this.lastSelectedEntity['definition']['schema']['category']
}
}
// Otherwise set entity mappings.
else
{
if (!this.entityMapped){
if (this.selectedEntityCount > 1){
this.name = '<Mixed>'
}else{
this.name = this.lastSelectedEntity['entityName']
}
console.log("entity not mapped")
this.updateMappingInputs()
this.getFamiliesFromSelectedMethod()
this.getTypesFromSelectedFamily()
this.selectedCategory = 49
} else {
if (this.selectedEntityCount > 1){
this.name = '<Mixed>'
}else{
this.name = this.lastSelectedEntity['schema']['name']
}
this.selectedMethod = this.lastSelectedEntity['schema']['method']
console.log("entity is mapped")
this.updateMappingInputs()
this.selectedFamily = this.lastSelectedEntity['schema']['family']
this.selectedCategory = this.lastSelectedEntity['schema']['category']
this.getFamiliesFromSelectedMethod()
this.getTypesFromSelectedFamily()
this.selectedFamilyType = this.lastSelectedEntity['schema']['family_type']
this.selectedLevel = this.lastSelectedEntity['schema']['level']
}
}
},
isEntityMapped(entity){
return entity['schema']['category'] !== undefined
},
isEntitiesMapped(entities){
return entities.every((entity) => this.isEntityMapped(entity))
},
isEntityDefinitionMapped(entity){
if (entity['definition'] === undefined){
return false
}
return entity['definition']['schema']['category'] !== undefined
},
isEntityDefinitionsMapped(entities){
return entities.every((entity) => this.isEntityDefinitionMapped(entity))
},
definitionSelectedHandler(state){
this.definitionSelected = state
this.setInputValuesFromSelection()
},
clickSelectionColumn(slotData) {
const indexExpanded = this.selectionExpandedIndexes.findIndex(i => i === slotData.item);
if (indexExpanded > -1) {
this.selectionExpandedIndexes.splice(indexExpanded, 1)
} else {
this.selectionExpandedIndexes.push(slotData.item);
}
},
clickMappedElementsColumn(slotData) {
const indexExpanded = this.mappedElementsExpandedIndexes.findIndex(i => i === slotData.item);
if (indexExpanded > -1) {
this.mappedElementsExpandedIndexes.splice(indexExpanded, 1)
} else {
this.mappedElementsExpandedIndexes.push(slotData.item);
}
},
inputsReadyToApply(){
if (this.selectedMethod === null || this.selectedMethod === undefined){
return false;
}
const nativeMethods = this.nativeEdgeMethods.concat(this.nativeFaceMethods)
if (this.selectedMethod === 'Direct Shape'){
return this.selectedCategory !== null
}
else if (nativeMethods.includes(this.selectedMethod)){
return this.selectedFamily !== null &&
this.selectedFamilyType !== null &&
this.selectedLevel !== null
}
else {
return true;
}
},
applyMapping(){
if (!this.inputsReadyToApply()){
this.$eventHub.$emit('error', {
text: 'Some inputs are not set to apply mapping.\n'
})
return
}
const mapping = {
entitiesToMap: this.selectedEntities.map((entity) => entity['entityId']),
method: this.selectedMethod,
category: this.selectedCategory,
family: this.selectedFamily,
familyType: this.selectedFamilyType,
level: this.selectedLevel,
name: this.name,
isDefinition: this.definitionSelected
}
sketchup.exec({name: "apply_mappings", data: mapping})
this.$eventHub.$emit('success', {
text: 'Mapping Applied.\n'
})
this.$mixpanel.track('MappingsAction', { name: 'Mappings Applied' })
},
clearMapping(){
const mapping = {
entitiesToClearMap: this.selectedEntities.map((entity) => entity['entityId']),
isDefinition: this.definitionSelected
}
sketchup.exec({name: "clear_mappings", data: mapping})
this.clearInputs()
this.$eventHub.$emit('error', {
text: 'Mapping Cleared.\n'
})
this.$mixpanel.track('MappingsAction', { name: 'Mappings Clear' })
},
clearMappingInputs(){
this.selectedMethod = null
this.selectedCategory = null
this.name = ""
this.selectedFamily = null
this.selectedFamilyType = null
this.selectedLevel = null
this.familyTypes = null
this.levels = null
this.availableMethods = null
this.availableCategories = null
this.allTypes = null
},
getDataFromSelection(selectionParameters){
this.availableMethods = selectionParameters.mappingMethods
this.availableCategories = selectionParameters.categories
this.selectedEntities = selectionParameters.selection
this.allTypes = selectionParameters.types
this.levels = selectionParameters.levels
this.selectedLevel = selectionParameters.selectedLevelName
},
updateStatesFromSelectionData(){
this.lastSelectedEntity = this.selectedEntities[this.selectedEntities.length - 1]
this.entityMapped = this.isEntitiesMapped(this.selectedEntities)
this.definitionMapped = this.isEntityDefinitionsMapped(this.selectedEntities)
this.definitionSelected = !this.entityMapped && this.definitionMapped
this.selectedEntityCount = this.selectedEntities.length
this.entitySelected = this.selectedEntityCount !== 0
}
},
mounted() {
sketchup.exec({name: "collect_mapped_entities", data: {}})
bus.$on('entities-selected', async (selectionParameters) => {
// Parse data to json object
const selectionPars = JSON.parse(selectionParameters)
// Reset mapping inputs with nulls and empties.
this.clearMappingInputs()
// Get data from selection into objects and arrays. These data basically constructs the dropdowns.
this.getDataFromSelection(selectionPars)
// Update inner state of the mapper component according to selection data.
this.updateStatesFromSelectionData()
// Get selection table data.
this.getSelectionTableData()
// Set mapping input values from selection data.
this.setInputValuesFromSelection()
})
bus.$on('entities-deselected', async () => {
this.entitySelected = false
this.clearInputs()
this.hideOptionalMappingInputs()
})
bus.$on('mapped-entities-updated', async (mappedEntities) => {
this.mappedEntityCount = mappedEntities.length
})
bus.$on('set-source-up-to-date', (isUpToDate) => {
this.sourceState = isUpToDate
})
}
}
</script>
<style scoped>
.btn-container{
display: flex;
flex-wrap: wrap;
}
.active .entity {
border: 2px solid green;
}
.v-card__title{
font-size: 1.02rem;
}
.v-card__subtitle{
font-size: 0.78rem;
}
.v-expansion-panel--active > .v-expansion-panel-header{
min-height: 32px;
}
.v-expansion-panel-header{
min-height: 32px;
padding: 12px 16px;
}
</style>
+227
View File
@@ -0,0 +1,227 @@
<template>
<v-container class="pa-0">
<v-autocomplete
v-model="sourceStreamId"
:label="streamText"
:items="allStreamsList"
item-text="name"
item-value="id"
density="compact"
></v-autocomplete>
<v-autocomplete
v-model="sourceBranchId"
class="pt-0 mb-n5"
:label="branchText"
:items="allBranchesList"
:disabled="sourceStreamId === null"
item-text="name"
item-value="id"
density="compact"
></v-autocomplete>
<v-container class="pa-0 mt-2">
<v-row justify="center" align="center">
<v-col cols="auto" class="pa-1 pb-2">
<v-btn
small
@click="applySource"
>
<v-icon dark left>
mdi-checkbox-marked-circle
</v-icon>Apply
</v-btn>
</v-col>
<v-col cols="auto" class="pa-1 pb-2">
<v-btn
small
:disabled="!sourceApplied"
@click="clearSource"
>
<v-icon dark left>
mdi-close-circle
</v-icon>Clear
</v-btn>
</v-col>
</v-row>
</v-container>
</v-container>
</template>
<script>
/*global sketchup*/
import gql from "graphql-tag";
import streamQuery from "@/graphql/stream.gql";
import {bus} from "@/main";
import ObjectLoader from "@speckle/objectloader";
const streamLimit = 20
export default {
name: "MappingSource",
props: {
streamSearchQuery: { type: String, default: null },
sourceState: { type: String, default: 'Not Set' },
streamText: {
type: String,
default: ''
},
branchText: {
type: String,
default: ''
}
},
data() {
return {
sourceApplied: false,
sourceStreamId: null,
sourceBranchId: null,
sourceStreamName: null,
sourceBranchName: null
}
},
apollo: {
streams: {
prefetch: true,
debounce: 300,
fetchPolicy: 'cache-and-network',
query: gql`
query Streams($query: String, $limit: Int, $cursor: String) {
streams(query: $query, limit: $limit, cursor: $cursor) {
totalCount
cursor
items {
id
name
}
}
}
`,
variables() {
return {
query: this.streamSearchQuery,
limit: streamLimit,
cursor: null
}
},
update(data) {
bus.$emit('streams-loaded')
this.showMoreEnabled = data.streams?.items.length < data.streams.totalCount
return data.streams
},
},
$subscribe: {
commitCreated: {
query: gql`
subscription ($streamId: String!) {
commitCreated(streamId: $streamId)
}
`,
variables() {
return {
streamId: this.sourceStreamId
}
},
result(data) {
if (data.data.commitCreated.sourceApplication.includes('Revit')){
if (data.data.commitCreated.branchName === this.selectedBranch.name){
this.afterCommitCreated()
this.$eventHub.$emit('notification', {
text: `A new commit was created on Revit!`,
})
this.$apollo.queries.stream.refetch()
}
}
},
skip() {
// Return true to skip the initial query execution
return this.sourceStreamId === null;
},
}
},
stream: {
query: streamQuery,
prefetch: true,
variables() {
return {
id: this.sourceStreamId
}
},
skip() {
// Return true to skip the initial query execution
return this.sourceStreamId === null;
},
}
},
computed: {
selectedBranch() {
if (!this.stream) return
return this.stream.branches.items.find((branch) => branch.id === this.sourceBranchId)
},
allStreamsList() {
if (this.$apollo.loading) return
return this.streams?.items
},
allBranchesList() {
if (this.$apollo.loading) return
return this.stream?.branches.items
},
},
mounted() {
bus.$on('refresh-source-branch', () => {
this.onSourceBranchChanged()
bus.$emit('set-source-up-to-date', 'Set')
})
},
methods: {
applySource(){
bus.$emit('set-source-up-to-date', 'Set')
this.onSourceBranchChanged()
this.$eventHub.$emit('success', {
text: 'Mapper source applied.\n'
})
this.$mixpanel.track('MappingsAction', { name: 'Mappings Source Apply' })
},
clearSource(){
sketchup.exec({name:"clear_mapper_source" , data: {}})
bus.$emit('set-source-up-to-date', 'Not Set')
this.sourceApplied = false
this.sourceBranchName = null
this.sourceStreamName = null
this.sourceBranchId = null
this.sourceStreamId = null
this.$eventHub.$emit('error', {
text: 'Mapper source cleared.\n'
})
},
afterCommitCreated(){
bus.$emit('set-source-up-to-date', 'Outdated')
},
async onSourceBranchChanged() {
const commitRefId = this.selectedBranch.commits.items[0]?.referencedObject
if (!commitRefId) { return }
const loader = new ObjectLoader({
serverUrl: localStorage.getItem('serverUrl'),
token: localStorage.getItem('SpeckleSketchup.AuthToken'),
streamId: this.sourceStreamId,
objectId: commitRefId
})
let rootObj = await loader.getAndConstructObject(this.updateLoadingStage)
sketchup.exec({name:"mapper_source_updated" , data: {
base: rootObj,
stream_id: this.sourceStreamId,
commit_id: commitRefId
}})
this.sourceApplied = true
}
}
}
</script>
<style scoped>
</style>
+22 -4
View File
@@ -13,7 +13,7 @@
<v-toolbar-title class="ml-0" style="position: relative; left: -10px">
<!-- Uncomment when pinning is in place and add style="position: relative; left: -10px" to the element above :) -->
<v-btn
v-tooltip="'Pin this stream - it will be saved to this file.'"
v-tooltip="`Pin this ${streamText.toLowerCase()} - it will be saved to this file.`"
icon
x-small
@click="toggleSavedStream"
@@ -66,7 +66,11 @@
<template #activator="{ on, attrs }">
<v-slide-x-transition>
<div v-show="hover">
<create-branch-dialog :stream-name="stream.name" :stream-id="streamId"/>
<create-branch-dialog
:is-f-e2="preferences && preferences.user && preferences.user.fe2"
:stream-name="stream.name"
:stream-id="streamId"
/>
</div>
</v-slide-x-transition>
<v-chip v-if="stream.branches" small v-bind="attrs" class="mr-1" v-on="on">
@@ -133,7 +137,7 @@
hide-details
dense
flat
placeholder="Write your commit message here"
:placeholder="`Write your ${commitText.toLowerCase()} message here`"
/>
</div>
</v-slide-y-transition>
@@ -204,7 +208,11 @@ export default {
commitId: 'latest',
commitMessage: null,
invalid: false,
diffing: false
diffing: false,
streamText: '',
branchText: '',
commitText: '',
preferences: {}
}
},
apollo: {
@@ -286,6 +294,16 @@ export default {
}
},
mounted() {
bus.$on('update-preferences', async (preferences) => {
const pref = JSON.parse(preferences)
this.preferences = pref
this.streamText = pref.user.fe2 ? 'Project' : 'Stream'
this.branchText = pref.user.fe2 ? 'Model' : 'Branch'
this.commitText = pref.user.fe2 ? 'Version' : 'Commit'
})
// Collect preferences to render UI according to it
sketchup.exec({name: "collect_preferences", data: {}})
bus.$on(`deactivate-diffing-${this.streamId}`, () => {
this.diffing = false
})
@@ -3,7 +3,7 @@
<v-dialog v-model="showCreateBranch">
<template #activator="{ on: dialog, attrs }">
<v-btn
v-tooltip="'Create Branch'"
v-tooltip="`Create ${isFE2 ? 'Model' : 'Branch'}`"
icon x-small class="ml-0 mr-1"
v-bind="attrs"
v-on="{...dialog}"
@@ -15,10 +15,10 @@
</template>
<v-card>
<v-card-title class="text-h5 mb-1">
Create a New Branch
{{ `Create a New ${isFE2 ? 'Model' : 'Branch'}` }}
</v-card-title>
<v-card-subtitle class="py-0 my-0 font-italic">
under {{ streamName }} stream
{{ `under ${streamName} ${isFE2 ? 'project' : 'stream'}` }}
</v-card-subtitle>
<v-container class="px-6" pb-0>
<v-text-field
@@ -27,7 +27,7 @@
hide-details
dense
flat
placeholder="Branch Name"
:placeholder="`${isFE2 ? 'Model' : 'Branch'} Name`"
/>
<v-text-field
v-model="description"
@@ -75,6 +75,14 @@ export default {
streamName: {
type: String,
default: null
},
isFE2: {
type: Boolean,
default: false
},
branchTooltipName: {
type: String,
default: ''
}
},
data() {
@@ -1,13 +1,13 @@
<template>
<v-container fluid class="px-1 pb-0 pt-1">
<v-container fluid class="px-1 pb-0 pt-0">
<v-row>
<v-col>
<v-col class="py-2">
<!-- DIALOG: Create New Stream -->
<v-dialog v-model="showCreateNewStream">
<template #activator="{ on, attrs }">
<v-btn
class="ma-2 pa-3"
x-small
small
v-bind="attrs"
v-on="on"
>
@@ -16,13 +16,14 @@
left
>
mdi-plus-circle
</v-icon>Create New Stream
</v-icon>
{{ `Create New ${isFE2 ? 'Project': 'Stream'}` }}
</v-btn>
</template>
<v-card>
<v-card-title class="text-h5">
Create a New Stream
{{ `Create a New ${isFE2 ? 'Project' : 'Stream'}` }}
</v-card-title>
<v-container class="px-6" pb-0>
<!--
@@ -55,7 +56,7 @@
hide-details
dense
flat
placeholder="Stream Name (Optional)"
:placeholder="`${isFE2 ? 'Project' : 'Stream'} Name (Optional)`"
/>
<v-text-field
v-model="description"
@@ -67,7 +68,7 @@
/>
<v-switch
v-model="privateStream"
:label="'Private Stream'"
:label="`Private ${isFE2 ? 'Project' : 'Stream'}`"
></v-switch>
</v-container>
@@ -96,7 +97,7 @@
<template #activator="{ on, attrs }">
<v-btn
class="ma-2 pa-3"
x-small
small
min-width="163"
v-bind="attrs"
v-on="on"
@@ -169,6 +170,10 @@ export default {
serverUrl: {
type: String,
default: null
},
isFE2: {
type: Boolean,
default: false
}
},
data() {
@@ -200,7 +205,7 @@ export default {
},
async getStream(){
try {
const streamWrapper = new StreamWrapper(this.createStreamByIdText, this.accountId, this.serverUrl)
const streamWrapper = new StreamWrapper(this.createStreamByIdText, this.accountId, this.serverUrl, this.isFE2)
let res = await this.$apollo.query({
query: gql`
query Stream($id: String!){

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