Compare commits
448 Commits
2.11.2
...
2.18.0-alpha7
| Author | SHA1 | Date | |
|---|---|---|---|
| 462eb229a5 | |||
| 9d37d126cb | |||
| 6372bea9fc | |||
| 060b1b8f41 | |||
| da2e228293 | |||
| ab7397bf55 | |||
| f79547f781 | |||
| 2152f1c90e | |||
| 503fb4d246 | |||
| 2befefa752 | |||
| 85e64c5076 | |||
| 60523dc994 | |||
| 6d780bf350 | |||
| eec02a1f84 | |||
| ace2fe6fe3 | |||
| 188794af8d | |||
| 92a941a944 | |||
| 0e1ddf2b11 | |||
| b57fa010d1 | |||
| f816452b78 | |||
| 120083bb31 | |||
| a5bb5c4686 | |||
| e5e2729f0a | |||
| ba8b902f48 | |||
| 2d67815ae6 | |||
| ec0c9066d2 | |||
| 58ae858077 | |||
| 613e7938b3 | |||
| e07ff1a445 | |||
| de7dd34ea2 | |||
| 0552f695f9 | |||
| b8d4f3d946 | |||
| fa112a70b1 | |||
| 97309ebb88 | |||
| 556ddc0b6f | |||
| a0dde690ea | |||
| a76dab5be6 | |||
| 2d10bc5bbf | |||
| 4042632e0b | |||
| 7ccf83e1a4 | |||
| 019cd0756f | |||
| 0e5f9f80be | |||
| fc6767860a | |||
| 5b5b4be7b2 | |||
| 45351d082e | |||
| 22ccd07491 | |||
| 2cf9ee647b | |||
| efb567824b | |||
| f0aac39486 | |||
| f278055805 | |||
| 6f2e36fd11 | |||
| 119d80ffc8 | |||
| 771c3df864 | |||
| 7d1963e458 | |||
| dde85972b3 | |||
| 5e061da910 | |||
| 46bea345de | |||
| bc53462ad6 | |||
| 884df40a1d | |||
| b23168c067 | |||
| 5568212f15 | |||
| 79db79d799 | |||
| 18a4008efd | |||
| 83e4abd1ee | |||
| 243bcfba72 | |||
| af2c8c560f | |||
| 6a37f3871c | |||
| 759a388448 | |||
| 360e89d7ce | |||
| 64655a3284 | |||
| c58356bde8 | |||
| f74117632d | |||
| 5f2b8b8e2b | |||
| f0ce7481fd | |||
| 6e7a5c6140 | |||
| 079c18ee19 | |||
| 26ef6a3815 | |||
| cf6dcefe6c | |||
| 35590eb979 | |||
| 583a8b8a76 | |||
| c6e8a664de | |||
| 24a5e0a579 | |||
| a242c197fb | |||
| 08bdd23149 | |||
| c5b35b2d98 | |||
| 5407fecd1f | |||
| 12d2821d26 | |||
| 2e6d58e6a3 | |||
| 0f0c0fd5ae | |||
| ef9ec1c223 | |||
| f4387bae30 | |||
| 49b238a23a | |||
| 4b75c01b28 | |||
| e7f641046b | |||
| 9aaabe0fab | |||
| b6e4b711bf | |||
| 58fcfd210b | |||
| 5868b9c234 | |||
| 0dc6d9cf9d | |||
| 67f50cf2fd | |||
| 8b26a4d49a | |||
| a1d0bb0aa1 | |||
| bfe08560b1 | |||
| ac3ac24272 | |||
| 03e7191d0e | |||
| b3a42f8723 | |||
| 9c4b740300 | |||
| 2a12bdadf2 | |||
| c90e8ad4d2 | |||
| 4a52c51c86 | |||
| 0efc817ddc | |||
| fee54fc98c | |||
| 210f751396 | |||
| 2e2bc3fe29 | |||
| a6f05f86d1 | |||
| fc144e4848 | |||
| cef9531428 | |||
| abd4faefbf | |||
| 966f7aaed5 | |||
| 51b59fa995 | |||
| 0b713736bd | |||
| 9e33581c66 | |||
| b97792b596 | |||
| c0746f8eff | |||
| a826a9d692 | |||
| 6d04203d37 | |||
| 33b2ed8a94 | |||
| 4f16da7ad0 | |||
| 36f92c7655 | |||
| 1d4f5a759e | |||
| 28af9bc811 | |||
| cf04cd4094 | |||
| 23e9efb28a | |||
| 57322df29c | |||
| fff82d34c6 | |||
| 7211860c21 | |||
| 9fc69044f8 | |||
| 76467c3e81 | |||
| 4cfc04e2f3 | |||
| 6bfc7771d0 | |||
| 7055135257 | |||
| 85312cf20d | |||
| 4051cae5b0 | |||
| c64bb6dedf | |||
| 7bb17ae0db | |||
| 3244eb9b15 | |||
| 6e173847ba | |||
| 8cbc1f763d | |||
| 2d45abeb28 | |||
| 2fd0ad77bc | |||
| 53c1167831 | |||
| 48d4a1d7bb | |||
| 4bbbba6e89 | |||
| 0a13575325 | |||
| d6075209df | |||
| 9d1b39422b | |||
| 38e297d32b | |||
| 459ef3c9f1 | |||
| 3bb12487ba | |||
| 8b2428c60d | |||
| f9befd9051 | |||
| 1020a9bd5d | |||
| 2844c4ea86 | |||
| 3344188fee | |||
| 67138c2f78 | |||
| f59d19ef21 | |||
| d0fa7e638f | |||
| 63011e466e | |||
| 92d1976300 | |||
| a0cdc9fa07 | |||
| 60f0006597 | |||
| 67a3c62a08 | |||
| 21d5dc1e0b | |||
| 0d810f59f9 | |||
| dba60b700f | |||
| ad9a56bd20 | |||
| 82585e9104 | |||
| dfbb241b0b | |||
| f69ea91f45 | |||
| 768d916092 | |||
| c0acecae10 | |||
| f741d1c7e4 | |||
| d25c70f1d1 | |||
| 6f954467ef | |||
| 867987a5f5 | |||
| 61e0a6351c | |||
| 558264c23b | |||
| cf7aa371af | |||
| ffedaf6a73 | |||
| d9ae272b36 | |||
| 3c5d1f5c9e | |||
| b69b8b7585 | |||
| 8c7e498fb2 | |||
| 359413a296 | |||
| cef7d894c4 | |||
| 7d9cc1aacc | |||
| a6780a756b | |||
| 4e37d17b42 | |||
| 793923348f | |||
| 700db1788d | |||
| 68a5a44702 | |||
| 464891f5a1 | |||
| d2e5db2680 | |||
| a9c41d0545 | |||
| 0bc458d307 | |||
| 36c7cc285e | |||
| f9fbd31a0f | |||
| 8789c1f855 | |||
| f65073480a | |||
| 157ed831a7 | |||
| 0799a59a15 | |||
| 60ea3e83e1 | |||
| 4b874deb50 | |||
| 6726b9ad50 | |||
| 75e6fe609e | |||
| b40d1ccf7d | |||
| 4f7ba23904 | |||
| 49084706f2 | |||
| 1852e5f6bf | |||
| be0ed5428a | |||
| f428efde6a | |||
| e79f04f33e | |||
| 04db8c281f | |||
| 2681698186 | |||
| 1682c66da6 | |||
| 38bc1032d8 | |||
| ba37eef9ca | |||
| 3db179882a | |||
| ba8f25a807 | |||
| eac3d3089b | |||
| 0454a9e147 | |||
| f390cd1c68 | |||
| aaa1ba5aa9 | |||
| fb9556abd9 | |||
| 3cfb3b1f84 | |||
| b071bab137 | |||
| 5dbc68ea76 | |||
| e76fbc3b77 | |||
| 09ca0514d1 | |||
| faec32a5bb | |||
| 1f5793ab79 | |||
| df22bd1cec | |||
| 61dba4e78d | |||
| bb678117f8 | |||
| 2dc08e71b7 | |||
| df2f844e74 | |||
| 621c602fc0 | |||
| f809169757 | |||
| 848b135612 | |||
| db6af66705 | |||
| 728e1f5a86 | |||
| d02d95bc9e | |||
| c1180c5373 | |||
| 4e01fb64e6 | |||
| d0fcb1da34 | |||
| 48352d06b3 | |||
| d995652569 | |||
| 5a958fa51c | |||
| cfb9e2cacd | |||
| 7932fc1cab | |||
| e0fc1715b5 | |||
| 1cfd6caa64 | |||
| 54bf18888d | |||
| 6b60fc4259 | |||
| 216af8697c | |||
| d97e7314d0 | |||
| 67d9dda143 | |||
| 5c60e303cb | |||
| e36b8f3a56 | |||
| 1e7a19b463 | |||
| 8004249a29 | |||
| 6cd800e4ef | |||
| e182bc7ed2 | |||
| c338d51c9d | |||
| c1912250df | |||
| 994c41980e | |||
| 385b7a514d | |||
| 3aaade9228 | |||
| b4dd19b711 | |||
| 9e9b83b4ba | |||
| 5ff5114669 | |||
| f63039f3fb | |||
| b39399463d | |||
| 8217ef7fbb | |||
| b4851c34e1 | |||
| 8e39832d3e | |||
| ce7ac6da16 | |||
| 57879a57cd | |||
| 6f5b367028 | |||
| 291948c1ef | |||
| 0ca6888fb1 | |||
| 5e92236396 | |||
| 28db40112b | |||
| b2153883ef | |||
| 807cbeb75d | |||
| 4730aebdc6 | |||
| c2279eec87 | |||
| f5567ae9ad | |||
| 2fcba04cf5 | |||
| 0eefd1605f | |||
| 7b88ea022d | |||
| 7a922114ab | |||
| f717c270fd | |||
| ee5ed468c3 | |||
| a9ded445a7 | |||
| 9ecad52df2 | |||
| 8c500985bf | |||
| 3567f9ba5d | |||
| 852bfb716c | |||
| 473956aa3c | |||
| b22fc19400 | |||
| f6a8ece992 | |||
| afc0c21aae | |||
| 08e6106c1f | |||
| 1f9f654e3d | |||
| 501e923760 | |||
| 37b716cfc5 | |||
| 8584db0ab5 | |||
| 5aff3e9692 | |||
| 5d8f03cbc3 | |||
| b6c85ac15a | |||
| c2af757f4e | |||
| 26252c55ac | |||
| 3ce547bcea | |||
| e0492f589e | |||
| 4931288ffd | |||
| 52e0f77a3c | |||
| 20c016f1bf | |||
| 00a271cbba | |||
| 3a6b69634d | |||
| 78cf7f42e3 | |||
| 2b52454c00 | |||
| e032e4561b | |||
| 23ce1e0476 | |||
| 825b7b98d7 | |||
| 3de3128a2a | |||
| 92d70ccc8b | |||
| f8c5154457 | |||
| 3a3874e9f1 | |||
| 61b94c519b | |||
| ac0f46ca66 | |||
| e538cdb509 | |||
| df8a63c5f5 | |||
| 6827a61bf8 | |||
| 20e56c05b3 | |||
| 5494139319 | |||
| c491ba087a | |||
| 5cf045c941 | |||
| 55fea5f077 | |||
| 3a9cb73950 | |||
| d99f613b79 | |||
| 68db8ddff3 | |||
| c7490b264b | |||
| a8e84cfad3 | |||
| 5ec4f7e4e8 | |||
| b019ce2f2e | |||
| dd90e41610 | |||
| bdf78e374d | |||
| 163197f583 | |||
| 8e13bba44a | |||
| e4478b86a5 | |||
| 1b1ae59ed7 | |||
| a54f44d22a | |||
| d2394274eb | |||
| 0eedb0b397 | |||
| 782e9db1f6 | |||
| 2977472c56 | |||
| 8f3a8bfa91 | |||
| 914f6e9516 | |||
| fa6f65cb7c | |||
| dafbe6f995 | |||
| 4a8ac6377b | |||
| c91fac4ec4 | |||
| 00d23b084e | |||
| e0a3b82cea | |||
| e49036d8c4 | |||
| 4a8356692b | |||
| adfb7bc63a | |||
| 3f41cefa88 | |||
| 2074ff1987 | |||
| 65fe189421 | |||
| bb7590eab4 | |||
| 6a04457219 | |||
| 7f8b1d9586 | |||
| 4362cc53bb | |||
| 959e6b2a12 | |||
| 2d91070e5a | |||
| cc038ece32 | |||
| 7af1292f29 | |||
| 2c3fd7a84f | |||
| d36f70dbc7 | |||
| 77f5f29c90 | |||
| ead45ed843 | |||
| a6259bb7eb | |||
| 0249ebeb95 | |||
| 1c3a35a137 | |||
| c12319b417 | |||
| 90ac00a628 | |||
| e5c71cb3a4 | |||
| ebf0681574 | |||
| 642643f412 | |||
| 8dd65ac255 | |||
| 8df58f226b | |||
| 58b4f2ce14 | |||
| 65f0c836fe | |||
| 2aa16085a5 | |||
| 25f05a69c6 | |||
| 5c2a94da16 | |||
| 162325e90e | |||
| b022a8c608 | |||
| 1e404f5e6b | |||
| 8ef7780332 | |||
| 51b4f7b3f7 | |||
| 9b3fa33e50 | |||
| 9349d0813d | |||
| 8d9bc500a1 | |||
| cae7b6e29f | |||
| 0b4ac732b2 | |||
| acacbb91e3 | |||
| fd6d3d9a2f | |||
| 3b531e30b1 | |||
| 0c78085b2e | |||
| 4ab53308f7 | |||
| 06ae161793 | |||
| 94e005d2f8 | |||
| 717072c3a5 | |||
| 02b4bde92a | |||
| a397d1e233 | |||
| d2198c0765 | |||
| 549bf63198 | |||
| 8b824f5342 | |||
| d052d5e8a1 | |||
| 610c22dd02 | |||
| a5496ab6a9 | |||
| c29c8f009c | |||
| fb7e9f2a6c | |||
| 62c2bbb9fa | |||
| 7656772194 | |||
| f22ff050e0 | |||
| 708f0b44fd | |||
| 2ee4581f17 | |||
| e337fb869f | |||
| 4a8b0147e1 | |||
| c95a1c7e1f | |||
| a07cd5c3f5 | |||
| 4d1473582e | |||
| 3bc9f4c452 | |||
| 11377038a0 |
+149
-13
@@ -36,24 +36,139 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: ./
|
||||
- run:
|
||||
name: Patch
|
||||
name: Create Innosetup signing cert
|
||||
shell: powershell.exe
|
||||
command:
|
||||
| # If no tag, use 0.0.0.1 and don't make any YML (for testing only!)
|
||||
$tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "0.0.0" } else { $env:CIRCLE_TAG }
|
||||
$semver = if($tag.Contains('/')) {$tag.Split("/")[1] } else { $tag }
|
||||
command: |
|
||||
echo $env:PFX_B64 > "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.txt"
|
||||
certutil -decode "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.txt" "speckle-sharp-ci-tools\SignTool\AEC Systems Ltd.pfx"
|
||||
- run:
|
||||
name: Set Environment Variable
|
||||
shell: powershell.exe
|
||||
command: |
|
||||
$tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "2.0.999" } else { $env:CIRCLE_TAG }
|
||||
$semver = if($tag.Contains('/')) {$tag.Split("/")[0] } else { $tag }
|
||||
$ver = if($semver.Contains('-')) {$semver.Split("-")[0] } else { $semver }
|
||||
$channel = if($semver.Contains('-')) {$semver.Split("-")[1] } else { "latest" }
|
||||
$version = "$($ver).$($env:CIRCLE_BUILD_NUM)"
|
||||
New-Item -Force "speckle-sharp-ci-tools/Installers/sketchup/$channel.yml" -ItemType File -Value "version: $semver"
|
||||
echo $version
|
||||
$version = "$($ver).$($env:WORKFLOW_NUM)"
|
||||
python patch_version.py $semver
|
||||
speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss
|
||||
environment:
|
||||
WORKFLOW_NUM: << pipeline.number >>
|
||||
- run:
|
||||
name: Build Installer
|
||||
command: speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss /Sbyparam=$p
|
||||
shell: cmd.exe #does not work in powershell
|
||||
|
||||
#- run:
|
||||
# name: Patch
|
||||
# shell: powershell.exe
|
||||
# command:
|
||||
# | # If no tag, use 0.0.0.1 and don't make any YML (for testing only!)
|
||||
# $tag = if([string]::IsNullOrEmpty($env:CIRCLE_TAG)) { "0.0.0" } else { $env:CIRCLE_TAG }
|
||||
# $semver = if($tag.Contains('/')) {$tag.Split("/")[1] } else { $tag }
|
||||
# $ver = if($semver.Contains('-')) {$semver.Split("-")[0] } else { $semver }
|
||||
# $channel = if($semver.Contains('-')) {$semver.Split("-")[1] } else { "latest" }
|
||||
# $version = "$($ver).$($env:CIRCLE_BUILD_NUM)"
|
||||
# New-Item -Force "speckle-sharp-ci-tools/Installers/sketchup/$channel.yml" -ItemType File -Value "version: $semver"
|
||||
# echo $version
|
||||
# python patch_version.py $semver
|
||||
# speckle-sharp-ci-tools\InnoSetup\ISCC.exe speckle-sharp-ci-tools\sketchup.iss
|
||||
- persist_to_workspace:
|
||||
root: ./
|
||||
paths:
|
||||
- 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
|
||||
@@ -77,6 +192,7 @@ jobs:
|
||||
root: ./
|
||||
paths:
|
||||
- speckle-sharp-ci-tools
|
||||
|
||||
deploy-manager2:
|
||||
docker:
|
||||
- image: mcr.microsoft.com/dotnet/sdk:6.0
|
||||
@@ -115,7 +231,6 @@ workflows:
|
||||
only: /.*/
|
||||
|
||||
- build-connector:
|
||||
context: innosetup
|
||||
slug: sketchup
|
||||
requires:
|
||||
- get-ci-tools
|
||||
@@ -123,6 +238,17 @@ workflows:
|
||||
filters:
|
||||
tags:
|
||||
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
|
||||
@@ -130,11 +256,21 @@ workflows:
|
||||
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
@@ -1,7 +1,7 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
addressable (2.8.1)
|
||||
addressable (2.8.4)
|
||||
public_suffix (>= 2.0.2, < 6.0)
|
||||
ast (2.4.2)
|
||||
axiom-types (0.1.1)
|
||||
@@ -26,7 +26,7 @@ GEM
|
||||
path_expander (~> 1.0)
|
||||
ruby_parser (~> 3.1, > 3.1.0)
|
||||
sexp_processor (~> 4.8)
|
||||
git (1.12.0)
|
||||
git (1.18.0)
|
||||
addressable (~> 2.8)
|
||||
rchardet (~> 1.8)
|
||||
ice_nine (0.11.2)
|
||||
@@ -48,8 +48,7 @@ GEM
|
||||
pry (0.14.1)
|
||||
coderay (~> 1.1)
|
||||
method_source (~> 1.0)
|
||||
psych (3.3.4)
|
||||
public_suffix (5.0.0)
|
||||
public_suffix (5.0.1)
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
rchardet (1.8.0)
|
||||
|
||||
@@ -3,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'
|
||||
|
||||
@@ -15,7 +15,9 @@ module SpeckleConnector
|
||||
def self.reload
|
||||
load(__FILE__)
|
||||
pattern = File.join(__dir__, '**/*.rb')
|
||||
Dir.glob(pattern).each { |file| load(file) }
|
||||
# TODO: Here is a opportunity to improve reloading process.
|
||||
# We can cache last edited time of the each file later to check which file need to be reloaded.
|
||||
Dir.glob(pattern).each { |file| load(file) unless file.include?('bootstrap') }
|
||||
.size
|
||||
end
|
||||
# rubocop:enable SketchupSuggestions/FileEncoding
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative 'deactivate_diffing'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Deactivate diffing for stream.
|
||||
class ActivateDiffing < Action
|
||||
def initialize(stream_id)
|
||||
super()
|
||||
@stream_id = stream_id
|
||||
end
|
||||
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def update_state(state)
|
||||
state = DeactivateDiffing.update_state(state, {})
|
||||
puts "Diffing activated for #{@stream_id}"
|
||||
speckle_entities = state.speckle_state.speckle_entities
|
||||
invalid_speckle_entities = speckle_entities.select do |_id, entity|
|
||||
entity.invalid_stream_ids.include?(@stream_id) && entity.sketchup_entity.is_a?(Sketchup::Face)
|
||||
end
|
||||
invalid_speckle_entities.each do |id, entity|
|
||||
new_entity = entity.activate_diffing(@stream_id, state.sketchup_state.materials.by_id(MAT_EDIT))
|
||||
speckle_entities = speckle_entities.put(id, new_entity)
|
||||
end
|
||||
new_speckle_state = state.speckle_state.with_speckle_entities(speckle_entities)
|
||||
state.with_speckle_state(new_speckle_state)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,52 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Adds material to speckle state and Sketchup.
|
||||
class AddMaterial < Action
|
||||
def self.update_state(state, material_name:, color:, material_id:, alpha: nil)
|
||||
materials = state.sketchup_state.materials
|
||||
existing_material = materials.by_id(material_id)
|
||||
return state if existing_material&.valid?
|
||||
|
||||
new_material = create_or_get_material(state.sketchup_state.sketchup_model,
|
||||
material_name,
|
||||
color,
|
||||
material_id,
|
||||
alpha: alpha)
|
||||
new_materials = materials.add_material(material_id, new_material)
|
||||
new_sketchup_state = state.sketchup_state.with(:@materials => new_materials)
|
||||
state.with(:@sketchup_state => new_sketchup_state)
|
||||
end
|
||||
|
||||
def self.create_or_get_material(model, material_name, color, material_id, alpha: nil)
|
||||
materials = model.materials
|
||||
existing_material = materials.find { |mat| mat.name == material_name }
|
||||
return existing_material if existing_material&.valid?
|
||||
|
||||
existing_material = materials.add material_name
|
||||
existing_material.set_attribute(MAT_DICTIONARY, MAT_ID, material_id.to_s)
|
||||
set_hex_color(existing_material, color)
|
||||
existing_material.alpha = alpha if alpha
|
||||
existing_material
|
||||
end
|
||||
|
||||
def self.set_hex_color(skp_material, hex_value)
|
||||
hex_value = hex_value.to_s
|
||||
col_blue, col_green, col_red = parse_hex_color(hex_value)
|
||||
skp_material.color = col_red, col_green, col_blue
|
||||
end
|
||||
|
||||
def self.parse_hex_color(hex_value)
|
||||
split_values = hex_value.match(/^#([a-fA-F\d]{2})([a-fA-F\d]{2})([a-fA-F\d]{2})$/) ||
|
||||
hex_value.match(/^#([a-fA-F\d])([a-fA-F\d])([a-fA-F\d])$/)
|
||||
col_red = split_values[1].hex
|
||||
col_green = split_values[2].hex
|
||||
col_blue = split_values[3].hex
|
||||
return col_blue, col_green, col_red
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,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,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Action to collect versions from sketchup and connector to track user's version by mixpanel.
|
||||
class CollectVersions < Action
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def self.update_state(state, _data)
|
||||
versions = {
|
||||
sketchup: Sketchup.version.to_i,
|
||||
speckle: SpeckleConnector::CONNECTOR_VERSION
|
||||
}
|
||||
state.with_add_queue('collectVersions', versions.to_json, [])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Deactivate diffing.
|
||||
class DeactivateDiffing < Action
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def self.update_state(state, _data)
|
||||
puts 'Diffing deactivated!'
|
||||
speckle_entities = state.speckle_state.speckle_entities
|
||||
diffing_activated_speckle_entities = speckle_entities.reject do |_id, entity|
|
||||
entity.active_diffing_stream_id.nil?
|
||||
end
|
||||
diffing_activated_speckle_entities.each do |id, entity|
|
||||
new_entity = entity.deactivate_diffing
|
||||
speckle_entities = speckle_entities.put(id, new_entity)
|
||||
end
|
||||
new_speckle_state = state.speckle_state.with_speckle_entities(speckle_entities)
|
||||
state.with_speckle_state(new_speckle_state)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,56 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_action'
|
||||
require_relative '../load_sketchup_model'
|
||||
require_relative '../collect_preferences'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
module Events
|
||||
# Handle events that are triggered by the {AppObserver}.
|
||||
class AppEventAction < EventAction
|
||||
# Handle loading new or existing model
|
||||
class OnNewOrChangedModel
|
||||
# Handle events when the new or existing model is loaded in Sketchup
|
||||
# @param state [States::State] the current state of speckle application
|
||||
# @param event_data [Array<(Sketchup::Model)>] the event data for the given event. It consists of
|
||||
# a double array with a single element that is the {Sketchup::Model} object of the loaded model.
|
||||
def self.update_state(state, event_data)
|
||||
return state unless event_data&.any?
|
||||
|
||||
model = event_data.flatten.first
|
||||
# LoadSketchupModel action should be responsible to update all model specific data for state and then
|
||||
# should notify the UI to update it's components.
|
||||
new_state = Actions::LoadSketchupModel.update_state(state, model)
|
||||
# Action to let UI to render itself with new preferences state
|
||||
# TODO: Later UI should be updated if any stream is invalid after
|
||||
# we collected speckle_entities appropriately
|
||||
CollectPreferences.update_state(new_state, {})
|
||||
end
|
||||
end
|
||||
|
||||
# Run actions that are needed before the sketchup quits
|
||||
class OnQuit
|
||||
# Handle when Sketchup application closes
|
||||
# @param state [States::State] the current state of speckle application
|
||||
# @param _event_data [Array] the event data
|
||||
# @return [States::State] the transformed state object
|
||||
def self.update_state(state, _event_data)
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
# Handlers that are used to handle specific events
|
||||
ACTIONS = {
|
||||
onNewModel: OnNewOrChangedModel,
|
||||
onOpenModel: OnNewOrChangedModel,
|
||||
onQuit: OnQuit
|
||||
}.freeze
|
||||
|
||||
def self.actions
|
||||
ACTIONS
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,84 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_action'
|
||||
require_relative '../../sketchup_model/utils/face_utils'
|
||||
require_relative '../../constants/dict_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
module Events
|
||||
# Event actions related to entities.
|
||||
class EntitiesEventAction < EventAction
|
||||
# Event action when element added.
|
||||
class OnElementAdded
|
||||
# @param state [States::State] the current state of the SpeckleConnector Application
|
||||
def self.update_state(state, event_data)
|
||||
modified_entities = event_data.to_a.collect { |e| e[1] }
|
||||
# do not copy speckle base object specific attributes, because they are entity specific
|
||||
modified_entities.each { |entity| entity.delete_attribute(SPECKLE_BASE_OBJECT) }
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
# Event action when element modified.
|
||||
class OnElementModified
|
||||
# @param state [States::State] the current state of the SpeckleConnector Application
|
||||
def self.update_state(state, event_data)
|
||||
speckle_state = state.speckle_state
|
||||
modified_entity = event_data[0][1]
|
||||
if modified_entity.is_a?(Sketchup::Face)
|
||||
path = state.sketchup_state.sketchup_model.active_path
|
||||
modified_faces = SketchupModel::Utils::FaceUtils.near_faces(modified_entity.edges)
|
||||
path_objects = path.nil? ? [] : path + path.collect(&:definition)
|
||||
parent_ids = path_objects.collect(&:persistent_id)
|
||||
ids_to_invalidate = modified_faces.collect(&:persistent_id) + parent_ids
|
||||
entities_to_invalidate = speckle_entities_to_invalidate(speckle_state, ids_to_invalidate)
|
||||
new_speckle_state = invalidate_speckle_entities(speckle_state, entities_to_invalidate)
|
||||
# This is the place we can send information to UI for diffing check
|
||||
diffing = state.user_state.preferences[:user][:diffing]
|
||||
new_speckle_state = new_speckle_state.with_invalid_streams_queue if diffing
|
||||
return state.with_speckle_state(new_speckle_state)
|
||||
end
|
||||
|
||||
state
|
||||
end
|
||||
|
||||
# @param speckle_state [States::SpeckleState] the current state of the Speckle
|
||||
def self.speckle_entities_to_invalidate(speckle_state, ids)
|
||||
speckle_state.speckle_entities.to_h.select { |id, _| ids.include?(id) }
|
||||
end
|
||||
|
||||
# @param speckle_state [States::SpeckleState] the current state of the Speckle
|
||||
def self.invalidate_speckle_entities(speckle_state, entities_to_invalidate)
|
||||
speckle_entities = speckle_state.speckle_entities
|
||||
entities_to_invalidate.each do |id, speckle_entity|
|
||||
edited_speckle_entity = speckle_entity.with_invalid
|
||||
speckle_entities = speckle_entities.put(id, edited_speckle_entity)
|
||||
end
|
||||
speckle_state.with_speckle_entities(speckle_entities)
|
||||
end
|
||||
end
|
||||
|
||||
# Event action when element removed.
|
||||
class OnElementRemoved
|
||||
# @param state [States::State] the current state of the SpeckleConnector Application
|
||||
def self.update_state(state, _event_data)
|
||||
# TODO: Do state updates when element removed
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
# Handlers that are used to handle specific events
|
||||
ACTIONS = {
|
||||
onElementRemoved: OnElementRemoved,
|
||||
onElementAdded: OnElementAdded,
|
||||
onElementModified: OnElementModified
|
||||
}.freeze
|
||||
|
||||
def self.actions
|
||||
ACTIONS
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,34 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# This module contains actions that are performed to handle events triggered by observers in Sketchup.
|
||||
module Events
|
||||
# Base action for Handling events
|
||||
class EventAction
|
||||
def self.actions
|
||||
raise NoMethodError, 'Implement in a subclass'
|
||||
end
|
||||
|
||||
# Handle the events that were collected by the observer. In case of the selection observer,
|
||||
# we only need to handle the events once if any of the events actually happened.
|
||||
# @param state [States::State] the current state of the SpeckleConnector Application
|
||||
# @param events [Hash{Symbol=>Array}] the event data grouped by the event name
|
||||
# @return [States::State] the transformed state
|
||||
def self.update_state(state, events)
|
||||
# Don't do anything if there are no events for this action
|
||||
return state unless events
|
||||
|
||||
actions = self.actions
|
||||
actions.each do |event_name, action|
|
||||
next unless events.key?(event_name)
|
||||
|
||||
event_data = events[event_name]
|
||||
state = action.update_state(state, event_data)
|
||||
end
|
||||
state
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,41 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_action'
|
||||
require_relative '../load_sketchup_model'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
module Events
|
||||
# Handle events that are triggered by the {ModelObserver}.
|
||||
class ModelEventAction < EventAction
|
||||
# Handle loading new or existing model
|
||||
class OnActivePathChanged
|
||||
# Handle events when the new or existing model is loaded in Sketchup
|
||||
# @param state [States::State] the current state of speckle application
|
||||
# @param event_data [Array<(Sketchup::Model)>] the event data for the given event. It consists of
|
||||
# a double array with a single element that is the {Sketchup::Model} object of the loaded model.
|
||||
def self.update_state(state, _event_data)
|
||||
sketchup_state = state.sketchup_state
|
||||
active_path = sketchup_state.sketchup_model.active_path
|
||||
observers = state.speckle_state.observers
|
||||
update_object_observers(active_path, observers)
|
||||
return state
|
||||
end
|
||||
|
||||
def self.update_object_observers(path, observers)
|
||||
path[-1].definition.entities.add_observer(observers[ENTITIES_OBSERVER]) unless path.nil?
|
||||
end
|
||||
end
|
||||
|
||||
# Handlers that are used to handle specific events
|
||||
ACTIONS = {
|
||||
onActivePathChanged: OnActivePathChanged
|
||||
}.freeze
|
||||
|
||||
def self.actions
|
||||
ACTIONS
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,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
|
||||
@@ -0,0 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative 'add_material'
|
||||
require_relative '../constants/mat_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Action to initialize materials
|
||||
class InitializeMaterials < Action
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def self.update_state(state)
|
||||
new_state = recreate_material(state, DEFAULT_NAMES[MAT_ADD], DEFAULT_COLORS[MAT_ADD], MAT_ADD)
|
||||
new_state = recreate_material(new_state, DEFAULT_NAMES[MAT_EDIT], DEFAULT_COLORS[MAT_EDIT], MAT_EDIT)
|
||||
recreate_material(new_state, DEFAULT_NAMES[MAT_REMOVE], DEFAULT_COLORS[MAT_REMOVE], MAT_REMOVE)
|
||||
end
|
||||
|
||||
def self.recreate_material(state, name, color, id, alpha: nil)
|
||||
Actions::AddMaterial.update_state(
|
||||
state,
|
||||
material_name: name,
|
||||
color: color,
|
||||
material_id: id,
|
||||
alpha: alpha
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,6 +6,7 @@ require_relative '../states/speckle_state'
|
||||
require_relative '../states/sketchup_state'
|
||||
require_relative '../accounts/accounts'
|
||||
require_relative '../preferences/preferences'
|
||||
require_relative '../constants/observer_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
@@ -13,14 +14,21 @@ module SpeckleConnector
|
||||
class InitializeSpeckle < Action
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def self.update_state(state)
|
||||
def self.update_state(state, observers)
|
||||
attach_app_observer!(observers[APP_OBSERVER])
|
||||
accounts = SpeckleConnector::Accounts.load_accounts
|
||||
speckle_state = States::SpeckleState.new(accounts, {}, {})
|
||||
speckle_state = States::SpeckleState.new(accounts, observers, {}, {})
|
||||
# This should be the only point that `Sketchup_active_model` passed to application state.
|
||||
sketchup_state = States::SketchupState.new(Sketchup.active_model)
|
||||
preferences = Preferences.init_preferences(sketchup_state.sketchup_model)
|
||||
preferences = Preferences.read_preferences(sketchup_state.sketchup_model)
|
||||
user_state_with_preferences = state.user_state.with_preferences(preferences)
|
||||
States::State.new(user_state_with_preferences, speckle_state, sketchup_state, false)
|
||||
state = States::State.new(user_state_with_preferences, speckle_state, sketchup_state, false)
|
||||
# This is where we attach observers to related model objects like selection, entities..
|
||||
Actions::LoadSketchupModel.update_state(state, sketchup_state.sketchup_model)
|
||||
end
|
||||
|
||||
def self.attach_app_observer!(observer)
|
||||
Sketchup.add_observer(observer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,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
|
||||
@@ -9,7 +9,8 @@ module SpeckleConnector
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def self.update_state(state, _data)
|
||||
(saved_streams = state.sketchup_state.sketchup_model.attribute_dictionary('speckle', true)['streams']) or []
|
||||
(saved_streams = state.sketchup_state.sketchup_model
|
||||
.attribute_dictionary('Speckle', true)['saved_streams']) or []
|
||||
state.with_add_queue('setSavedStreams', saved_streams, [])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
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'
|
||||
require_relative '../constants/observer_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Switch sketchup model wit a new one
|
||||
class LoadSketchupModel < Action
|
||||
# Replace current model state with the state of a new model. This action is triggered when user opens new or
|
||||
# existing Sketchup model.
|
||||
# @param state [States::State] the current state of Speckle
|
||||
# @param additional_parameters [Array] parameters that the action takes
|
||||
# @return [States::State] the new updated state object
|
||||
def self.update_state(state, sketchup_model)
|
||||
# Init sketchup state again with new model
|
||||
new_sketchup_state = States::SketchupState.new(sketchup_model)
|
||||
sketchup_model.rendering_options['DisplaySectionPlanes'] = true
|
||||
new_state = state.with(:@sketchup_state => new_sketchup_state)
|
||||
# Init materials again
|
||||
new_state = InitializeMaterials.update_state(new_state)
|
||||
|
||||
# Read speckle entities
|
||||
new_speckle_entities = SketchupModel::Reader::SpeckleEntitiesReader.read(sketchup_model.entities)
|
||||
new_speckle_state = new_state.speckle_state.with_speckle_entities(Immutable::Hash.new(new_speckle_entities))
|
||||
# Read mapped entities
|
||||
new_mapped_entities = SketchupModel::Reader::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.
|
||||
preferences = Preferences.read_preferences(new_state.sketchup_state.sketchup_model)
|
||||
new_user_state = new_state.user_state.with_preferences(preferences)
|
||||
new_state = new_state.with(:@user_state => new_user_state)
|
||||
attach_observers(sketchup_model, new_state.speckle_state.observers)
|
||||
new_state
|
||||
end
|
||||
|
||||
# Attach observers to the sketchup model
|
||||
# @param sketchup_model [Sketchup::Model] the model to attach observers to
|
||||
# @param observers [Hash{Class=>}] the observer objects indexed by their class that will be attached
|
||||
def self.attach_observers(sketchup_model, observers)
|
||||
selection = sketchup_model.selection
|
||||
selection.add_observer(observers[SELECTION_OBSERVER])
|
||||
# layers = sketchup_model.layers
|
||||
# layers.add_observer(observers[LAYERS_OBSERVER_NAME])
|
||||
entities = sketchup_model.entities
|
||||
entities.add_observer(observers[ENTITIES_OBSERVER])
|
||||
sketchup_model.add_observer(observers[MODEL_OBSERVER])
|
||||
# materials = sketchup_model.materials
|
||||
# materials.add_observer(observers[MATERIALS_OBSERVER_NAME])
|
||||
# pages = sketchup_model.pages
|
||||
# pages.add_observer(observers[PAGES_OBSERVER_NAME])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative '../sketchup_model/reader/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,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative '../mapper/category/revit_category'
|
||||
require_relative '../mapper/category/revit_family_category'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Collects mapper selection info.
|
||||
class MapperInitialized < 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)
|
||||
init_parameters = {
|
||||
categories: Mapper::Category::RevitCategory.to_a,
|
||||
familyCategories: Mapper::Category::RevitFamilyCategory.to_a
|
||||
}.freeze
|
||||
state.with_mapper_init_queue(init_parameters)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,200 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative '../mapper/category/revit_category'
|
||||
require_relative '../mapper/category/revit_family_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)
|
||||
source_exist = !state.speckle_state.speckle_mapper_state.mapper_source.nil?
|
||||
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.
|
||||
return multiple_supported_selection_info(selection) if supported_entity_count > 1
|
||||
|
||||
# FIXME: Distinguish selection info according to selection elegantly!!!
|
||||
if grouped_by_type.keys.first == Sketchup::ComponentInstance
|
||||
return component_selection_info(selection, source_exist)
|
||||
end
|
||||
|
||||
return group_selection_info(selection) if grouped_by_type.keys.first == Sketchup::Group
|
||||
|
||||
if supported_entity_count > 1 ||
|
||||
(supported_entity_count == 1 &&
|
||||
MAPPER_DIRECT_SHAPE_SUPPORTED_ENTITY_TYPES.include?(grouped_by_type.keys.first))
|
||||
if source_exist
|
||||
return direct_shape_selection_info_with_source(selection, [])
|
||||
else
|
||||
return direct_shape_selection_info(selection, source_exist)
|
||||
end
|
||||
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: []
|
||||
}.freeze
|
||||
|
||||
def multiple_supported_selection_info(selection)
|
||||
{
|
||||
selection: SketchupModel::Reader::MapperReader.entities_schema_details(selection),
|
||||
mappingMethods: ['Direct Shape']
|
||||
}.freeze
|
||||
end
|
||||
|
||||
def component_selection_info(selection, source_exist)
|
||||
if source_exist
|
||||
{
|
||||
selection: SketchupModel::Reader::MapperReader.entities_schema_details(selection),
|
||||
mappingMethods: ['Direct Shape', 'New Revit Family', 'Family Instance']
|
||||
}.freeze
|
||||
else
|
||||
{
|
||||
selection: SketchupModel::Reader::MapperReader.entities_schema_details(selection),
|
||||
mappingMethods: ['Direct Shape', 'New Revit Family']
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
|
||||
def group_selection_info(selection)
|
||||
{
|
||||
selection: SketchupModel::Reader::MapperReader.entities_schema_details(selection),
|
||||
mappingMethods: ['Direct Shape']
|
||||
}.freeze
|
||||
end
|
||||
|
||||
def direct_shape_selection_info(selection, source_exist)
|
||||
methods = ['Direct Shape', 'New Revit Family']
|
||||
methods.append('Family Instance') if source_exist
|
||||
{
|
||||
selection: SketchupModel::Reader::MapperReader.entities_schema_details(selection),
|
||||
mappingMethods: methods
|
||||
}.freeze
|
||||
end
|
||||
|
||||
def direct_shape_selection_info_with_default(selection, methods)
|
||||
{
|
||||
selection: SketchupModel::Reader::MapperReader.entities_schema_details(selection),
|
||||
mappingMethods: ['Direct Shape'] + methods
|
||||
}.freeze
|
||||
end
|
||||
|
||||
def direct_shape_selection_info_with_source(filtered_selection, methods)
|
||||
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,
|
||||
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, source_exist) if grouped_by_verticality.length == 2
|
||||
|
||||
if source_exist
|
||||
if grouped_by_verticality.keys.first
|
||||
direct_shape_selection_info_with_source(faces, ['Wall'])
|
||||
else
|
||||
direct_shape_selection_info_with_source(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(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,77 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative '../constants/type_constants'
|
||||
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'])
|
||||
family_instances = convert_family_instance_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 },
|
||||
{ is_string: false, val: family_instances.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_family_instance_types(types)
|
||||
family_instance_types = {}
|
||||
types.each do |type, type_elements|
|
||||
next if type_elements.nil? || !type_elements.is_a?(Array) || type == '__closure'
|
||||
|
||||
# skip type if there is no any revit symbol element type
|
||||
symbol_element_types = type_elements.select do |t|
|
||||
t['speckle_type'] == OBJECTS_BUILTELEMENTS_REVIT_REVITSYMBOLELEMENTTYPE &&
|
||||
t['placementType'] == 'OneLevelBased'
|
||||
end
|
||||
next if symbol_element_types.empty?
|
||||
|
||||
elements = type_elements.map do |type_element|
|
||||
SpeckleObjects::BuiltElements::Revit::RevitElementType.to_native(type_element)
|
||||
end
|
||||
elements = elements.group_by { |e| e[:family] }
|
||||
family_instance_types.merge!(elements)
|
||||
end
|
||||
family_instance_types
|
||||
end
|
||||
|
||||
def convert_levels(state, levels)
|
||||
levels.collect do |level|
|
||||
SpeckleObjects::BuiltElements::Level.to_native(state, level, @stream_id)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,35 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative 'events/app_event_action'
|
||||
require_relative 'events/entities_event_action'
|
||||
require_relative 'events/model_event_action'
|
||||
require_relative 'events/selection_event_action'
|
||||
require_relative '../constants/observer_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Handle events that were collected by observers
|
||||
class OnEventsAction < Action
|
||||
RUN_ORDER = {
|
||||
APP_OBSERVER => Events::AppEventAction,
|
||||
ENTITIES_OBSERVER => Events::EntitiesEventAction,
|
||||
MODEL_OBSERVER => Events::ModelEventAction,
|
||||
# MATERIALS_OBSERVER => Events::MaterialsEventAction,
|
||||
# LAYERS_OBSERVER => Events::LayerEventAction,
|
||||
# PAGES_OBSERVER => Events::PagesEventAction,
|
||||
SELECTION_OBSERVER => Events::SelectionEventAction
|
||||
}.freeze
|
||||
|
||||
def self.update_state(state, events)
|
||||
RUN_ORDER.each do |observer_name, action|
|
||||
next unless events.key?(observer_name)
|
||||
|
||||
parameters = events[observer_name]
|
||||
state = action.update_state(state, parameters)
|
||||
end
|
||||
state
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,29 +3,39 @@
|
||||
require_relative 'action'
|
||||
require_relative '../convertors/units'
|
||||
require_relative '../convertors/to_native'
|
||||
require_relative '../convertors/clean_up'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Action to receive objects from Speckle Server.
|
||||
class ReceiveObjects < Action
|
||||
def initialize(stream_id, base, stream_name, branch_name, branch_id)
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def initialize(stream_id, base, stream_name, branch_name, branch_id, source_app)
|
||||
super()
|
||||
@stream_id = stream_id
|
||||
@base = base
|
||||
@stream_name = stream_name
|
||||
@branch_name = branch_name
|
||||
@branch_id = branch_id
|
||||
@source_app = source_app
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def update_state(state)
|
||||
converter = Converters::ToNative.new(state.sketchup_state.sketchup_model)
|
||||
converter = Converters::ToNative.new(state, @stream_id, @stream_name, @branch_name, @source_app)
|
||||
# Have side effects on the sketchup model. It effects directly on the entities by adding new objects.
|
||||
start_time = Time.now.to_f
|
||||
converter.receive_commit_object(@base, state.user_state.preferences[:model])
|
||||
state.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}. ===="
|
||||
state.with_add_queue('finishedReceiveInSketchup', @stream_id, [])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -18,10 +18,10 @@ module SpeckleConnector
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def update_state(state)
|
||||
speckle_dict = state.sketchup_state.sketchup_model.attribute_dictionary('speckle', true)
|
||||
saved = speckle_dict['streams'] || []
|
||||
speckle_dict = state.sketchup_state.sketchup_model.attribute_dictionary('Speckle', true)
|
||||
saved = speckle_dict['saved_streams'] || []
|
||||
saved -= [@stream_id]
|
||||
speckle_dict['streams'] = saved
|
||||
speckle_dict['saved_streams'] = saved
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
@@ -16,10 +16,10 @@ module SpeckleConnector
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def update_state(state)
|
||||
speckle_dict = state.sketchup_state.sketchup_model.attribute_dictionary('speckle', true)
|
||||
saved = speckle_dict['streams'] || []
|
||||
speckle_dict = state.sketchup_state.sketchup_model.attribute_dictionary('Speckle', true)
|
||||
saved = speckle_dict['saved_streams'] || []
|
||||
saved = saved.empty? ? [@stream_id] : saved.unshift(@stream_id)
|
||||
speckle_dict['streams'] = saved
|
||||
speckle_dict['saved_streams'] = saved
|
||||
state
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,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
|
||||
@@ -16,11 +16,10 @@ module SpeckleConnector
|
||||
# @return [States::State] the new updated state object
|
||||
def update_state(state)
|
||||
to_send_stream_id = state.speckle_state.stream_queue[:stream_id]
|
||||
return state if to_send_stream_id == @stream_id
|
||||
return state if to_send_stream_id == @stream_id || to_send_stream_id.nil?
|
||||
|
||||
to_send_converted = state.speckle_state.stream_queue[:converted].to_json
|
||||
new_state = state.with_add_queue('convertedFromSketchup', to_send_stream_id, [to_send_converted])
|
||||
new_state = new_state.with_add_queue('oneClickSend', to_send_stream_id, [])
|
||||
new_state.with_empty_stream_queue
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
require_relative 'deactivate_diffing'
|
||||
require_relative '../convertors/units'
|
||||
require_relative '../convertors/to_speckle'
|
||||
|
||||
@@ -16,16 +17,23 @@ module SpeckleConnector
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def update_state(state)
|
||||
sketchup_model = state.sketchup_state.sketchup_model
|
||||
converter = Converters::ToSpeckle.new(sketchup_model)
|
||||
base = converter.convert_selection_to_base(state.user_state.preferences)
|
||||
id, total_children_count, batches = converter.send_info(base)
|
||||
state = DeactivateDiffing.update_state(state, {})
|
||||
converter = Converters::ToSpeckle.new(state, @stream_id)
|
||||
new_speckle_state, base = converter.convert_selection_to_base(state.user_state.preferences)
|
||||
id, total_children_count, batches, new_speckle_state = converter.serialize(base, new_speckle_state,
|
||||
state.user_state.preferences)
|
||||
puts("converted #{base.count} objects for stream #{@stream_id}")
|
||||
state.with_add_queue('convertedFromSketchup', @stream_id, [
|
||||
{ is_string: false, val: batches },
|
||||
{ is_string: true, val: id },
|
||||
{ is_string: false, val: total_children_count }
|
||||
])
|
||||
|
||||
# This is the place we can send information to UI for diffing check
|
||||
diffing = state.user_state.preferences[:user][:diffing]
|
||||
new_speckle_state = new_speckle_state.with_invalid_streams_queue if diffing
|
||||
|
||||
new_state = state.with_speckle_state(new_speckle_state)
|
||||
new_state.with_add_queue('convertedFromSketchup', @stream_id, [
|
||||
{ is_string: false, val: batches },
|
||||
{ is_string: true, val: id },
|
||||
{ is_string: false, val: total_children_count }
|
||||
])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'action'
|
||||
|
||||
module SpeckleConnector
|
||||
module Actions
|
||||
# Show all entities on the model.
|
||||
class ShowAllEntities < Action
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
def self.update_state(state, _data)
|
||||
# Show all entities first
|
||||
state.sketchup_state.sketchup_model.entities.each do |ent|
|
||||
ent.hidden = false
|
||||
end
|
||||
state
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -18,6 +18,8 @@ module SpeckleConnector
|
||||
|
||||
# @param state [States::State] the current state of the {App::SpeckleConnectorApp}
|
||||
# @return [States::State] the new updated state object
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def update_state(state)
|
||||
# Init sqlite database
|
||||
db = Sqlite3::Database.new(SPECKLE_CONFIG_DB_PATH)
|
||||
@@ -46,8 +48,19 @@ module SpeckleConnector
|
||||
user[@preference.to_sym] = @value
|
||||
new_preferences = state.user_state.preferences.put(:user, user)
|
||||
new_user_state = state.user_state.with_preferences(new_preferences)
|
||||
# This is the place we can send information to UI for diffing check. It is a technical depth!
|
||||
if @preference == 'diffing'
|
||||
new_speckle_state = if @value
|
||||
state.speckle_state.with_invalid_streams_queue
|
||||
else
|
||||
state.speckle_state.with_empty_invalid_streams_queue
|
||||
end
|
||||
state = state.with_speckle_state(new_speckle_state)
|
||||
end
|
||||
state.with_user_state(new_user_state)
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,6 +15,9 @@ module SpeckleConnector
|
||||
# @return [Ui::UiController] controller for ui views
|
||||
attr_reader :ui_controller
|
||||
|
||||
# @return [Observers::Handler] the observers indexed by their classes to handle
|
||||
attr_reader :observer_handler
|
||||
|
||||
def initialize(menu_commands, state, ui_controller)
|
||||
@menu_commands = menu_commands
|
||||
@state = state
|
||||
@@ -29,12 +32,20 @@ module SpeckleConnector
|
||||
ui_controller.update_ui(state)
|
||||
end
|
||||
|
||||
# Attach observers to application when speckle initialized via menu commands.
|
||||
def add_observer_handler!(observer_handler)
|
||||
@observer_handler = observer_handler
|
||||
end
|
||||
|
||||
# Send messages to HtmlDialog if any.
|
||||
def send_messages!
|
||||
queue = @state.speckle_state.message_queue
|
||||
queue.each_value { |value| ui_controller.user_interfaces[Ui::SPECKLE_UI_ID].dialog.execute_script(value) }
|
||||
update_state!(Actions::ClearQueue)
|
||||
end
|
||||
|
||||
# This is the only function application state will be switched by calling upcoming action with it's parameters
|
||||
# if any.
|
||||
def update_state!(action, *parameters)
|
||||
old_state = @state
|
||||
@state = action.update_state(old_state, *parameters)
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'command'
|
||||
require_relative '../actions/activate_diffing'
|
||||
|
||||
module SpeckleConnector
|
||||
module Commands
|
||||
# Command to activate diffing for stream.
|
||||
class ActivateDiffing < Command
|
||||
def _run(data)
|
||||
stream_id = data['stream_id']
|
||||
action = Actions::ActivateDiffing.new(stream_id)
|
||||
app.update_state!(action)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,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
|
||||
@@ -18,11 +18,22 @@ module SpeckleConnector
|
||||
|
||||
def run(*parameters)
|
||||
# Run here common operations that same for each command.
|
||||
_run(*parameters)
|
||||
with_observers_disabled do
|
||||
_run(*parameters)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def with_observers_disabled(&block)
|
||||
observer_handler = @app.observer_handler
|
||||
if observer_handler
|
||||
observer_handler.with_observers_disabled(&block)
|
||||
else
|
||||
block.call
|
||||
end
|
||||
end
|
||||
|
||||
def _run(*_parameters)
|
||||
raise NotImplementedError, 'Implement in subclass'
|
||||
end
|
||||
|
||||
@@ -4,6 +4,7 @@ require_relative 'command'
|
||||
require_relative '../states/initial_state'
|
||||
require_relative '../ui/vue_view'
|
||||
require_relative '../actions/initialize_speckle'
|
||||
require_relative '../observers/factory'
|
||||
|
||||
module SpeckleConnector
|
||||
module Commands
|
||||
@@ -30,7 +31,10 @@ module SpeckleConnector
|
||||
# Do the actual Speckle initialization.
|
||||
def initialize_speckle(app)
|
||||
# TODO: Initialize here speckle states and observers.
|
||||
app.update_state!(Actions::InitializeSpeckle)
|
||||
observer_handler = Observers::Factory.create_handler(app)
|
||||
app.add_observer_handler!(observer_handler)
|
||||
observers = Observers::Factory.create_observers(observer_handler)
|
||||
app.update_state!(Actions::InitializeSpeckle, observers)
|
||||
dialog_specs = {
|
||||
dialog_id: Ui::SPECKLE_UI_ID,
|
||||
htm_file: Ui::VUE_UI_HTML,
|
||||
|
||||
@@ -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
|
||||
@@ -13,7 +13,8 @@ module SpeckleConnector
|
||||
branch_name = data['branch_name']
|
||||
branch_id = data['branch_id']
|
||||
stream_name = data['stream_name']
|
||||
action = Actions::ReceiveObjects.new(stream_id, base, stream_name, branch_name, branch_id)
|
||||
source_app = data['source_app']
|
||||
action = Actions::ReceiveObjects.new(stream_id, base, stream_name, branch_name, branch_id, source_app)
|
||||
app.update_state!(action)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'command'
|
||||
require_relative '../states/initial_state'
|
||||
require_relative '../ui/vue_view'
|
||||
require_relative '../actions/initialize_speckle'
|
||||
require_relative '../observers/factory'
|
||||
|
||||
module SpeckleConnector
|
||||
module Commands
|
||||
# Command to reset Speckle UI window location onto center of SketchUp window.
|
||||
class ResetWindowLocation < Command
|
||||
|
||||
private
|
||||
|
||||
def _run
|
||||
app = self.app
|
||||
vue_view = app.ui_controller.user_interfaces[Ui::SPECKLE_UI_ID]
|
||||
if vue_view
|
||||
vue_view.dialog.reset_dialog_location
|
||||
else
|
||||
puts "Speckle UI didn't initialized!"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,6 +3,7 @@
|
||||
require_relative 'menu_command_handler'
|
||||
require_relative 'action_command'
|
||||
require_relative 'initialize_speckle'
|
||||
require_relative 'reset_window_location'
|
||||
require_relative '../actions/one_click_send'
|
||||
|
||||
module SpeckleConnector
|
||||
@@ -10,6 +11,7 @@ module SpeckleConnector
|
||||
# Speckle menu commands that adds them to Sketchup menu and toolbar.
|
||||
class SpeckleMenuCommands
|
||||
CMD_INITIALIZE_SPECKLE = :initialize_speckle
|
||||
CMD_RESET_WINDOW_LOCATION_SPECKLE = :reset_window_location_speckle
|
||||
CMD_SEND_TO_SPECKLE = :send_to_speckle
|
||||
CMD_RECEIVE_FROM_SPECKLE = :receive_from_speckle
|
||||
|
||||
@@ -26,6 +28,9 @@ module SpeckleConnector
|
||||
commands.add_to_menu!(CMD_INITIALIZE_SPECKLE, speckle_menu)
|
||||
commands.add_to_toolbar!(CMD_INITIALIZE_SPECKLE, speckle_toolbar)
|
||||
|
||||
commands[CMD_RESET_WINDOW_LOCATION_SPECKLE] = reset_window_location_command(app)
|
||||
commands.add_to_menu!(CMD_RESET_WINDOW_LOCATION_SPECKLE, speckle_menu)
|
||||
|
||||
# commands[CMD_SEND_TO_SPECKLE] = send_command(app)
|
||||
# commands.add_to_menu!(CMD_SEND_TO_SPECKLE, speckle_menu)
|
||||
# commands.add_to_toolbar!(CMD_SEND_TO_SPECKLE, speckle_toolbar)
|
||||
@@ -42,6 +47,17 @@ module SpeckleConnector
|
||||
cmd
|
||||
end
|
||||
|
||||
def self.reset_window_location_command(app)
|
||||
cmd = MenuCommandHandler.sketchup_command(
|
||||
ResetWindowLocation.new(app), 'Reset Window Location'
|
||||
)
|
||||
cmd.tooltip = 'Bring Speckle window onto center of SketchUp window'
|
||||
cmd.status_bar_text = 'Bring Speckle window onto center of SketchUp window'
|
||||
cmd.small_icon = '../../img/s2logo.png'
|
||||
cmd.large_icon = '../../img/s2logo.png'
|
||||
cmd
|
||||
end
|
||||
|
||||
def self.send_command(app)
|
||||
cmd = MenuCommandHandler.sketchup_command(
|
||||
ActionCommand.new(app, Actions::OneClickSend), 'Send to Speckle'
|
||||
|
||||
@@ -1,8 +1,16 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SpeckleConnector
|
||||
SPECKLE_BASE_OBJECT = 'Speckle_Base_Object'
|
||||
SPECKLE_MAPPING_TOOL_SCHEMA = 'Speckle_Mapping_Tool_Schema'
|
||||
SPECKLE_SCHEMA = 'Speckle_Schema'
|
||||
|
||||
SPECKLE_ID = 'speckle_id'
|
||||
SPECKLE_TYPE = 'speckle_type'
|
||||
APPLICATION_ID = 'application_id'
|
||||
TOTAL_CHILDREN_COUNT = 'total_children_count'
|
||||
CHILDREN = 'children'
|
||||
PARENT = 'parent'
|
||||
VALID_STREAM_IDS = 'valid_stream_ids'
|
||||
INVALID_STREAM_IDS = 'invalid_stream_ids'
|
||||
end
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SpeckleConnector
|
||||
VECTOR_Z = Geom::Vector3d.new(0, 0, 1)
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SpeckleConnector
|
||||
MAT_DICTIONARY = 'Speckle_Connector_Materials'
|
||||
MAT_ID = 'Speckle_Connector_Material_Id'
|
||||
|
||||
MAT_ADD = :speckle_connector_add_material
|
||||
MAT_EDIT = :speckle_connector_edit_material
|
||||
MAT_REMOVE = :speckle_connector_remove_material
|
||||
|
||||
DEFAULT_COLORS = {
|
||||
MAT_ADD => '#66FF66',
|
||||
MAT_EDIT => '#FFFF9F',
|
||||
MAT_REMOVE => '#FF6666'
|
||||
}.freeze
|
||||
|
||||
DEFAULT_NAMES = {
|
||||
MAT_ADD => 'Speckle_Material_Add',
|
||||
MAT_EDIT => 'Speckle_Material_Edit',
|
||||
MAT_REMOVE => 'Speckle_Material_Remove'
|
||||
}.freeze
|
||||
end
|
||||
@@ -0,0 +1,8 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SpeckleConnector
|
||||
APP_OBSERVER = 'SpeckleConnector::Observers::AppObserver'
|
||||
ENTITIES_OBSERVER = 'SpeckleConnector::Observers::EntitiesObserver'
|
||||
MODEL_OBSERVER = 'SpeckleConnector::Observers::ModelObserver'
|
||||
SELECTION_OBSERVER = 'SpeckleConnector::Observers::SelectionObserver'
|
||||
end
|
||||
@@ -7,13 +7,14 @@ require_relative 'platform_constants'
|
||||
module SpeckleConnector
|
||||
dir = __dir__.dup
|
||||
dir.force_encoding('UTF-8') if dir.respond_to?(:force_encoding)
|
||||
HOME_PATH = (ENV['HOME']).to_s
|
||||
SPECKLE_SRC_PATH = Pathname.new(File.expand_path('..', dir)).cleanpath.to_s
|
||||
SPECKLE_APPDATA_PATH = case OPERATING_SYSTEM
|
||||
when OS_WIN
|
||||
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
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../speckle_objects/geometry/length'
|
||||
|
||||
module SpeckleConnector
|
||||
COMBINE_FACES_BY_MATERIAL = :combine_faces_by_material
|
||||
INCLUDE_ENTITY_ATTRIBUTES = :include_entity_attributes
|
||||
INCLUDE_FACE_ENTITY_ATTRIBUTES = :include_face_entity_attributes
|
||||
INCLUDE_EDGE_ENTITY_ATTRIBUTES = :include_edge_entity_attributes
|
||||
INCLUDE_GROUP_ENTITY_ATTRIBUTES = :include_group_entity_attributes
|
||||
INCLUDE_COMPONENT_ENTITY_ATTRIBUTES = :include_component_entity_attributes
|
||||
MERGE_COPLANAR_FACES = :merge_coplanar_faces
|
||||
|
||||
ENTITY_KEYS_FOR_INCLUDING_ATTRIBUTES = {
|
||||
Sketchup::ComponentInstance => INCLUDE_COMPONENT_ENTITY_ATTRIBUTES,
|
||||
Sketchup::Group => INCLUDE_GROUP_ENTITY_ATTRIBUTES,
|
||||
Sketchup::Face => INCLUDE_FACE_ENTITY_ATTRIBUTES,
|
||||
Sketchup::Edge => INCLUDE_EDGE_ENTITY_ATTRIBUTES
|
||||
}.freeze
|
||||
|
||||
LEVEL_SHIFT_VALUE = SpeckleObjects::Geometry.length_to_native(1.5, 'm')
|
||||
|
||||
DEFAULT_MODEL_PREFERENCES = {
|
||||
COMBINE_FACES_BY_MATERIAL => true,
|
||||
INCLUDE_ENTITY_ATTRIBUTES => true,
|
||||
INCLUDE_FACE_ENTITY_ATTRIBUTES => true,
|
||||
INCLUDE_EDGE_ENTITY_ATTRIBUTES => true,
|
||||
INCLUDE_GROUP_ENTITY_ATTRIBUTES => true,
|
||||
INCLUDE_COMPONENT_ENTITY_ATTRIBUTES => true,
|
||||
MERGE_COPLANAR_FACES => true
|
||||
}.freeze
|
||||
end
|
||||
@@ -2,4 +2,40 @@
|
||||
|
||||
module SpeckleConnector
|
||||
BASE_OBJECT = 'Base'
|
||||
|
||||
OBJECTS_GIS_POLYGONELEMENT = 'Objects.GIS.PolygonElement'
|
||||
OBJECTS_GIS_LINEELEMENT = 'Objects.GIS.LineElement'
|
||||
|
||||
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_FAMILY_INSTANCE = 'Objects.BuiltElements.Revit.FamilyInstance'
|
||||
OBJECTS_BUILTELEMENTS_REVIT_PARAMETER = 'Objects.BuiltElements.Revit.Parameter'
|
||||
OBJECTS_BUILTELEMENTS_REVIT_REVITELEMENTTYPE = 'Objects.BuiltElements.Revit.RevitElementType'
|
||||
OBJECTS_BUILTELEMENTS_REVIT_REVITSYMBOLELEMENTTYPE = 'Objects.BuiltElements.Revit.RevitElementType:Objects.BuiltElements.Revit.RevitSymbolElementType'
|
||||
|
||||
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'
|
||||
|
||||
OBJECTS_OTHER_BLOCKINSTANCE = 'Objects.Other.BlockInstance'
|
||||
OBJECTS_OTHER_BLOCKINSTANCE_FULL = 'Objects.Other.Instance:Objects.Other.BlockInstance'
|
||||
OBJECTS_OTHER_INSTANCE = 'Objects.Other.Instance:Objects.Other.Instance'
|
||||
OBJECTS_OTHER_REVIT_REVITINSTANCE = 'Objects.Other.Revit.RevitInstance'
|
||||
OBJECTS_OTHER_BLOCKDEFINITION = 'Objects.Other.BlockDefinition'
|
||||
OBJECTS_OTHER_RENDERMATERIAL = 'Objects.Other.RenderMaterial'
|
||||
OBJECTS_OTHER_DISPLAYSTYLE = 'Objects.Other.DisplayStyle'
|
||||
|
||||
SPECKLE_CORE_MODELS_COLLECTION = 'Speckle.Core.Models.Collection'
|
||||
SPECKLE_CORE_MODELS_COLLECTION_RASTER_LAYER = 'Speckle.Core.Models.Collection:Objects.GIS.RasterLayer'
|
||||
SPECKLE_CORE_MODELS_COLLECTION_VECTOR_LAYER = 'Speckle.Core.Models.Collection:Objects.GIS.VectorLayer'
|
||||
end
|
||||
|
||||
@@ -5,6 +5,7 @@ require 'securerandom'
|
||||
# rubocop:enable SketchupPerformance/OpenSSL
|
||||
require 'digest'
|
||||
require_relative 'converter'
|
||||
require_relative '../speckle_entities/speckle_entity'
|
||||
require_relative '../relations/many_to_one_relation'
|
||||
|
||||
module SpeckleConnector
|
||||
@@ -15,7 +16,16 @@ module SpeckleConnector
|
||||
# @return [Integer] default chunk size the determine splitting base prop into chucks
|
||||
attr_reader :default_chunk_size
|
||||
|
||||
def initialize(default_chunk_size = 1000)
|
||||
# @return [String] stream id to send conversion
|
||||
attr_reader :stream_id
|
||||
|
||||
attr_accessor :speckle_state
|
||||
|
||||
# @param stream_id [String] stream id to send conversion
|
||||
def initialize(speckle_state, stream_id, preferences, default_chunk_size = 1000)
|
||||
@speckle_state = speckle_state
|
||||
@stream_id = stream_id
|
||||
@preferences = preferences
|
||||
@default_chunk_size = default_chunk_size
|
||||
@detach_lineage = []
|
||||
@lineage = []
|
||||
@@ -30,31 +40,55 @@ module SpeckleConnector
|
||||
def serialize(base)
|
||||
id, traversed = traverse_base(base)
|
||||
@objects[id] = traversed
|
||||
return id, traversed
|
||||
id
|
||||
end
|
||||
|
||||
def total_children_count(id)
|
||||
@objects[id][:totalChildrenCount]
|
||||
end
|
||||
|
||||
# @param base [Object] base object to populate all children and their relationship
|
||||
# @param base_and_entities [Object] base object to populate all children and their relationship
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def traverse_base(base)
|
||||
# rubocop:disable Metrics/PerceivedComplexity
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def traverse_base(base_and_entities)
|
||||
base, entities = base_and_entities
|
||||
|
||||
# 1. Create random string for lineage tracking.
|
||||
@lineage.append(SecureRandom.hex)
|
||||
|
||||
# 2. Initialize traversed base object that will be filled with traversed values or
|
||||
# 2. Get last item from detach_lineage array
|
||||
is_detached = @detach_lineage.pop
|
||||
|
||||
# unless entities.nil?
|
||||
# is_sent_before = entities.all? do |entity|
|
||||
# check_base_available_on_state(entity, speckle_state)
|
||||
# end
|
||||
# if is_sent_before
|
||||
# speckle_entity = speckle_state.speckle_entities[entities.first.persistent_id]
|
||||
# ref_object = detach_helper(speckle_entity.id)
|
||||
# parent = @lineage[-1]
|
||||
# unless @family_tree[parent].nil?
|
||||
# @family_tree[parent] = @family_tree[parent].merge(speckle_entity.speckle_object[:__closure])
|
||||
# end
|
||||
# @objects[speckle_entity.id] = ref_object if is_detached
|
||||
# return speckle_entity.id, ref_object
|
||||
# end
|
||||
# end
|
||||
|
||||
# 3. Initialize traversed base object that will be filled with traversed values or
|
||||
# traversed base objects as props.
|
||||
traversed_base = SpeckleObjects::Base.new(speckle_type: base[:speckle_type], id: '')
|
||||
|
||||
# 3.1 Remove applicationId if it is nil
|
||||
traversed_base.delete(:applicationId)
|
||||
|
||||
# 3. Iterate all entries (key, value) of the base {Base > Hash} object
|
||||
# 4. Iterate all entries (key, value) of the base {Base > Hash} object
|
||||
# speckle_state = traverse_base_props(base, traversed_base)
|
||||
traverse_base_props(base, traversed_base)
|
||||
# this is where all props are done for current `traversed_base`
|
||||
|
||||
# 4. Get last item from detach_lineage array
|
||||
is_detached = @detach_lineage.pop
|
||||
|
||||
# 5. Add closures
|
||||
closure = {}
|
||||
parent = @lineage.pop
|
||||
@@ -82,9 +116,19 @@ module SpeckleConnector
|
||||
# 10. Save object string if detached
|
||||
@objects[id] = traversed_base if is_detached
|
||||
|
||||
if @preferences[:user][:register_speckle_entity] && !entities.nil?
|
||||
entities.uniq.each do |entity|
|
||||
speckle_entity = create_or_update_speckle_entity(entity, id, traversed_base)
|
||||
@speckle_state = speckle_state.with_speckle_entity(speckle_entity)
|
||||
end
|
||||
end
|
||||
|
||||
return id, traversed_base
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
# rubocop:enable Metrics/PerceivedComplexity
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
@@ -102,14 +146,16 @@ module SpeckleConnector
|
||||
next
|
||||
end
|
||||
|
||||
# 3.3. Determine prop is detached or not
|
||||
is_prop_detach = prop[0] == '@'
|
||||
# 3.3. Determine prop is dynamically detached or not
|
||||
is_detach_prop = prop[0] == '@'
|
||||
is_dynamically_detached = prop[0] == '@' && prop.length > 2 && prop[1] == '@'
|
||||
prop = prop[2..-1] if is_dynamically_detached
|
||||
|
||||
# 3.4. Check prop needs to split into chunks
|
||||
chunked_detach_match = prop.match(/^@\((\d*)\)/)
|
||||
|
||||
# 3.5. If split chunk is needed and prop value is array, then run chunking process
|
||||
if value.is_a?(Array) && chunked_detach_match
|
||||
if value.is_a?(Array) && !base_and_entities?(value) && chunked_detach_match
|
||||
# 3.5.1. Determine chunk size, get it from prop if defined. ex: '@(31250)faces' -> 31250 = chunk size
|
||||
chunk_size = chunked_detach_match[1] == '' ? default_chunk_size : chunked_detach_match[1].to_i
|
||||
|
||||
@@ -144,7 +190,7 @@ module SpeckleConnector
|
||||
chunk_references = []
|
||||
|
||||
chunks.each do |chunk_element|
|
||||
@detach_lineage.append(is_prop_detach)
|
||||
@detach_lineage.append(is_detach_prop)
|
||||
id, _traversed = traverse_base(chunk_element)
|
||||
chunk_references.append(detach_helper(id))
|
||||
end
|
||||
@@ -152,17 +198,21 @@ module SpeckleConnector
|
||||
# 3.5.7. Add chunk references to the traversed base prop without @(<chunk_size>)
|
||||
traversed_base[prop.to_s.sub(chunked_detach_match[0], '')] = chunk_references
|
||||
|
||||
# 3.5.8. We are done chunking, good to go next
|
||||
# 3.5.8. We are done chunking, good to go next property
|
||||
next
|
||||
end
|
||||
|
||||
child = traverse_value(value, is_detach_prop)
|
||||
|
||||
is_base = (value.is_a?(Hash) && !value[:speckle_type].nil?) ||
|
||||
(base_and_entities?(value) && value[0].is_a?(Hash) && !value[0][:speckle_type].nil?)
|
||||
|
||||
# 3.6. traverse value according to value is a speckle object or not
|
||||
if value.is_a?(Hash) && !value[:speckle_type].nil?
|
||||
child = traverse_value(value, is_prop_detach)
|
||||
traversed_base[prop] = is_prop_detach ? detach_helper(child[:id]) : child
|
||||
else
|
||||
traversed_base[prop] = traverse_value(value, is_prop_detach)
|
||||
end
|
||||
traversed_base[prop] = if is_base
|
||||
is_detach_prop ? detach_helper(child[:id]) : child
|
||||
else
|
||||
child
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
@@ -171,38 +221,59 @@ module SpeckleConnector
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
# rubocop:enable Metrics/PerceivedComplexity
|
||||
|
||||
# Whether value has a pattern [<converted>, [<entity>, <entity>, ... <entity>]] or not.
|
||||
def base_and_entities?(value)
|
||||
is_array = value.is_a?(Array)
|
||||
return false unless is_array
|
||||
|
||||
return false unless is_array && value.length == 2
|
||||
|
||||
return false if value[1].nil?
|
||||
|
||||
value[1].all? { |v| v.is_a?(Sketchup::Entity) }
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
# rubocop:disable Metrics/PerceivedComplexity
|
||||
# rubocop:disable Style/OptionalBooleanParameter
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def traverse_value(value, is_detach = false)
|
||||
# 1. Return same value if value is primitive type (string, numeric, boolean)
|
||||
return value unless value.is_a?(Hash) || value.is_a?(Array)
|
||||
|
||||
# 2. Arrays
|
||||
if value.is_a?(Array)
|
||||
# 2. For pure arrays (Without referencing any Sketchup Entity)
|
||||
if value.is_a?(Array) && !base_and_entities?(value)
|
||||
|
||||
# 2.1. If it is not detached then iterate array by traversing with their value
|
||||
return value.collect { |el| traverse_value(el) } unless is_detach
|
||||
unless is_detach
|
||||
values = value.collect do |el|
|
||||
el_value = traverse_value(el)
|
||||
el_value
|
||||
end
|
||||
return values
|
||||
end
|
||||
|
||||
# 2.2. If it is detached than collect them into detached_list
|
||||
detached_list = []
|
||||
value.each do |el|
|
||||
if (el.is_a?(Array) || el.is_a?(Hash)) && !el[:speckle_type].nil?
|
||||
if (el.is_a?(Hash) && !el[:speckle_type].nil?) || base_and_entities?(el)
|
||||
@detach_lineage.append(is_detach)
|
||||
id, _traversed_base = traverse_base(el)
|
||||
detached_list.append(detach_helper(id))
|
||||
else
|
||||
detached_list.append(traverse_value(el, is_detach))
|
||||
el_value = traverse_value(el, is_detach)
|
||||
detached_list.append(el_value)
|
||||
end
|
||||
end
|
||||
return detached_list
|
||||
end
|
||||
|
||||
# 3. Hash
|
||||
return value if value[:speckle_type].nil?
|
||||
return value if value.is_a?(Hash) && value[:speckle_type].nil?
|
||||
|
||||
# 4. Base objects
|
||||
unless value[:speckle_type].nil?
|
||||
if (value.is_a?(Hash) && !value[:speckle_type].nil?) || base_and_entities?(value)
|
||||
@detach_lineage.append(is_detach)
|
||||
_id, traversed_base = traverse_base(value)
|
||||
return traversed_base
|
||||
@@ -215,6 +286,7 @@ module SpeckleConnector
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
# rubocop:enable Metrics/PerceivedComplexity
|
||||
# rubocop:enable Style/OptionalBooleanParameter
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
def detach_helper(reference_id)
|
||||
@lineage.each do |parent|
|
||||
@@ -263,6 +335,32 @@ module SpeckleConnector
|
||||
batches
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
# @param entity [Sketchup::Entity] source entity object
|
||||
# @param speckle_state [States::SpeckleState] the current speckle state of the {States::State}
|
||||
def check_base_available_on_state(entity, speckle_state)
|
||||
is_exist = speckle_state.speckle_entities.keys.include?(entity.persistent_id)
|
||||
return is_exist unless is_exist
|
||||
|
||||
speckle_state.speckle_entities[entity.persistent_id].valid_stream_ids.include?(stream_id)
|
||||
end
|
||||
|
||||
# Creates or updates speckle entity.
|
||||
# If speckle entity exist in state, creates new one by updating old one.
|
||||
# Else creates new one
|
||||
# @return [SpeckleEntity] speckle entity that collects both speckle and sketchup information.
|
||||
def create_or_update_speckle_entity(entity, id, traversed_base)
|
||||
if speckle_state.speckle_entities.keys.include?(entity.persistent_id)
|
||||
speckle_state.speckle_entities[entity.persistent_id].with_valid_stream_id(stream_id)
|
||||
else
|
||||
children = traversed_base[:__closure].nil? ? {} : traversed_base[:__closure]
|
||||
speckle_entity = SpeckleEntities::SpeckleEntity.new(entity, id, entity.persistent_id,
|
||||
traversed_base[:speckle_type],
|
||||
children.keys, [stream_id])
|
||||
speckle_entity.write_initial_base_data
|
||||
speckle_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -11,12 +11,36 @@ 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
|
||||
|
||||
def self.merged_faces(faces)
|
||||
faces.reject(&:deleted?)
|
||||
end
|
||||
|
||||
# Detect edges to remove by checking following controls respectively;
|
||||
@@ -28,43 +52,34 @@ 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 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)
|
||||
begin
|
||||
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)
|
||||
|
||||
# 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)
|
||||
# Check faces are coplanar or not.
|
||||
return false unless faces_coplanar?(face_1, face_2)
|
||||
|
||||
edge.erase!
|
||||
true
|
||||
edge.erase!
|
||||
true
|
||||
rescue StandardError => e
|
||||
puts "Failed to merge coplanar faces by removing edge with error: #{e}"
|
||||
false
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
|
||||
# Determines if two faces are overlapped.
|
||||
def self.face_duplicate?(face_1, face_2, overlapping: false)
|
||||
|
||||
@@ -4,18 +4,32 @@ module SpeckleConnector
|
||||
module Converters
|
||||
# Helper class to convert geometries between server and Sketchup.
|
||||
class Converter
|
||||
# @return [States::State] the current state of the {SpeckleConnector::App}
|
||||
attr_reader :state
|
||||
|
||||
# @return [States::SpeckleState] the current speckle state of the {States::State}
|
||||
attr_reader :speckle_state
|
||||
|
||||
# @return [Sketchup::Model] active sketchup model.
|
||||
attr_reader :sketchup_model
|
||||
|
||||
attr_accessor :units, :definitions, :registry, :entity_observer
|
||||
# @return [String] stream id that conversion happening with it
|
||||
attr_reader :stream_id
|
||||
|
||||
def initialize(sketchup_model)
|
||||
@sketchup_model = sketchup_model
|
||||
su_unit = @sketchup_model.options['UnitsOptions']['LengthUnit']
|
||||
# @return [String] speckle units
|
||||
attr_reader :units
|
||||
|
||||
attr_accessor :definitions
|
||||
|
||||
# @param state [States::State] the current state of the {SpeckleConnector::App}
|
||||
def initialize(state, stream_id)
|
||||
@state = state
|
||||
@speckle_state = state.speckle_state
|
||||
@sketchup_model = state.sketchup_state.sketchup_model
|
||||
@stream_id = stream_id
|
||||
su_unit = state.sketchup_state.length_units
|
||||
@units = Converters::SKETCHUP_UNITS[su_unit]
|
||||
@definitions = {}
|
||||
# @registry = Sketchup.active_model.attribute_dictionary("speckle_id_registry", true)
|
||||
# @entity_observer = SpeckleEntityObserver.new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,41 +1,252 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
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/gis/line_element'
|
||||
require_relative '../speckle_objects/other/transform'
|
||||
require_relative '../speckle_objects/other/render_material'
|
||||
require_relative '../speckle_objects/other/block_definition'
|
||||
require_relative '../speckle_objects/other/block_instance'
|
||||
require_relative '../speckle_objects/other/display_value'
|
||||
require_relative '../speckle_objects/revit/revit_instance'
|
||||
require_relative '../speckle_objects/geometry/point'
|
||||
require_relative '../speckle_objects/geometry/line'
|
||||
require_relative '../speckle_objects/geometry/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 '../speckle_objects/speckle/core/models/gis_layer_collection'
|
||||
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
module Converters
|
||||
# Converts sketchup entities to speckle objects.
|
||||
# rubocop:disable Metrics/ClassLength
|
||||
class ToNative < Converter
|
||||
# @return [States::SpeckleState] the current speckle state of the {States::State}
|
||||
attr_accessor :speckle_state
|
||||
|
||||
# @return [String] source application of received object that will be converted to native
|
||||
attr_reader :source_app
|
||||
|
||||
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 = REVIT::Other::RevitInstance
|
||||
REVIT_WALL = BUILTELEMENTS::RevitWall
|
||||
RENDER_MATERIAL = OTHER::RenderMaterial
|
||||
DISPLAY_VALUE = OTHER::DisplayValue
|
||||
VIEW3D = BUILTELEMENTS::View3d
|
||||
POLYGON_ELEMENT = GIS::PolygonElement
|
||||
LINE_ELEMENT = GIS::LineElement
|
||||
COLLECTION = SpeckleObjects::Speckle::Core::Models::Collection
|
||||
GIS_LAYER_COLLECTION = SpeckleObjects::Speckle::Core::Models::GisLayerCollection
|
||||
|
||||
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
|
||||
Objects.Other.Revit.RevitInstance
|
||||
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
|
||||
Objects.GIS.LineElement
|
||||
Speckle.Core.Models.Collection
|
||||
Speckle.Core.Models.Collection:Objects.GIS.RasterLayer
|
||||
Speckle.Core.Models.Collection:Objects.GIS.VectorLayer
|
||||
].freeze
|
||||
|
||||
def can_convert_to_native(obj)
|
||||
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)
|
||||
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' }
|
||||
|
||||
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)
|
||||
bbox = @entities_to_fill.parent.bounds
|
||||
c_1 = bbox.corner(0)
|
||||
c_2 = bbox.corner(1)
|
||||
c_3 = bbox.corner(3)
|
||||
c_4 = bbox.corner(2)
|
||||
section_planes.each do |section_plane|
|
||||
level_name = "#{@definition_name}-#{section_plane.name}"
|
||||
definition = sketchup_model.definitions.add(level_name)
|
||||
instance = @entities_to_fill.add_instance(definition, Geom::Transformation.new)
|
||||
att = section_plane.attribute_dictionary(SPECKLE_BASE_OBJECT).to_h
|
||||
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.set_hash(instance, att)
|
||||
elevation = section_plane.bounds.center.z
|
||||
c1_e = Geom::Point3d.new(c_1.x, c_1.y, elevation - LEVEL_SHIFT_VALUE)
|
||||
c2_e = Geom::Point3d.new(c_2.x, c_2.y, elevation - LEVEL_SHIFT_VALUE)
|
||||
c3_e = Geom::Point3d.new(c_3.x, c_3.y, elevation - LEVEL_SHIFT_VALUE)
|
||||
c4_e = Geom::Point3d.new(c_4.x, c_4.y, elevation - LEVEL_SHIFT_VALUE)
|
||||
cline_1 = definition.entities.add_cline(c1_e, c2_e)
|
||||
cline_2 = definition.entities.add_cline(c2_e, c3_e)
|
||||
cline_3 = definition.entities.add_cline(c3_e, c4_e)
|
||||
cline_4 = definition.entities.add_cline(c4_e, c1_e)
|
||||
text = definition.entities.add_text(" #{section_plane.name}", c1_e)
|
||||
[cline_1, cline_2, cline_3, cline_4, text, definition].each { |o| o.layer = levels_layer }
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
# @return [Sketchup::ComponentDefinition] branch definition to fill objects in it.
|
||||
def branch_definition
|
||||
@definition_name = "#{@branch_name}-#{@stream_name}"
|
||||
definition = sketchup_model.definitions.find { |d| d.name == @definition_name }
|
||||
@is_update_commit = !definition.nil?
|
||||
definition = sketchup_model.definitions.add(@definition_name) if definition.nil?
|
||||
definition
|
||||
end
|
||||
|
||||
def entities_to_fill(_obj)
|
||||
return sketchup_model.entities unless from_revit
|
||||
|
||||
@definition_name = "#{@branch_name}-#{@stream_name}"
|
||||
definition = sketchup_model.definitions.find { |d| d.name == @definition_name }
|
||||
if definition.nil?
|
||||
definition = sketchup_model.definitions.add(@definition_name)
|
||||
sketchup_model.entities.add_instance(definition, Geom::Transformation.new)
|
||||
end
|
||||
definition
|
||||
end
|
||||
|
||||
LAYERS_WILL_BE_HIDDEN = [
|
||||
'Rooms',
|
||||
'Mass',
|
||||
'Mass Floor',
|
||||
'Grid',
|
||||
'Shaft Openings'
|
||||
].freeze
|
||||
|
||||
def check_hiding_layers_needed
|
||||
return unless from_revit
|
||||
|
||||
sketchup_model.layers.each do |layer|
|
||||
if LAYERS_WILL_BE_HIDDEN.any? { |layer_name| layer.display_name.include?(layer_name) }
|
||||
layer.visible = false
|
||||
sketchup_model.pages.each { |page| page.update(PAGE_USE_LAYER_VISIBILITY) }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Conditions for converting speckle object to native sketchup entity:
|
||||
# 1- `obj` is a hash
|
||||
# 2- `obj` has a property as 'speckle_type'
|
||||
# 3- `obj` is a convertable 'speckle_type' which sketchup supports
|
||||
# @param obj [Object] candidate object to convert from speckle to sketchup.
|
||||
# @return [Boolean] whether object is convertable or not.
|
||||
def convertible_to_native?(obj)
|
||||
return false unless obj.is_a?(Hash) && obj.key?('speckle_type')
|
||||
|
||||
CONVERTABLE_SPECKLE_TYPES.include?(obj['speckle_type'])
|
||||
@@ -45,90 +256,14 @@ module SpeckleConnector
|
||||
['Objects.BuiltElements.Revit.Parameter'].include?(obj['speckle_type'])
|
||||
end
|
||||
|
||||
# @param obj [Object] speckle commit object.
|
||||
def receive_commit_object(obj, model_preferences)
|
||||
# First create layers on the sketchup before starting traversing
|
||||
filtered_layer_containers = obj.keys.filter_map { |key| key if key.start_with?('@') && key != '@Named Views' }
|
||||
create_layers(filtered_layer_containers, sketchup_model.layers)
|
||||
create_views(obj.filter_map { |key, value| value if key == '@Named Views' }, sketchup_model)
|
||||
# Define default commit layer which is the fallback
|
||||
default_commit_layer = sketchup_model.layers.layers.find { |layer| layer.display_name == '@Untagged' }
|
||||
traverse_commit_object(obj, sketchup_model.layers, default_commit_layer, model_preferences)
|
||||
end
|
||||
|
||||
# Create actual Sketchup layers from layer_paths that taken from Speckle base object.
|
||||
# @param layer_paths [Array<String>] layer paths to decompose it to folders and it's layers.
|
||||
# @param folder [Sketchup::Layers, Sketchup::LayerFolder] folder to create folders and layers under it.
|
||||
def create_layers(layer_paths, folder)
|
||||
# Strip leading '@'
|
||||
layers_with_folders = layer_paths.map { |layer| layer[1..-1] }
|
||||
# Split layer_paths according to having parent folder or not.
|
||||
layers_with_head_folder, headless_layers = layers_with_folders.partition { |layer| layer.include?('::') }
|
||||
# Create array of array that split with '::'
|
||||
folder_layer_arrays = layers_with_head_folder.collect { |folder_layer| folder_layer.split('::') }
|
||||
# Add headless layers into `Sketchup.active_model.layers`
|
||||
create_headless_layers(headless_layers, folder)
|
||||
# Create layers that have parent folder(s)- this method is recursive until all tree is created.
|
||||
create_folder_layers(folder_layer_arrays, folder)
|
||||
end
|
||||
|
||||
# @param views [Array] views.
|
||||
# @param sketchup_model [Sketchup::Model] active sketchup model.
|
||||
def create_views(views, sketchup_model)
|
||||
return if views.empty?
|
||||
|
||||
views.first.each do |view|
|
||||
origin = view['origin']
|
||||
target = view['target']
|
||||
origin = SpeckleObjects::Geometry::Point.to_native(origin['x'], origin['y'], origin['z'], origin['units'])
|
||||
target = SpeckleObjects::Geometry::Point.to_native(target['x'], target['y'], target['z'], target['units'])
|
||||
# Set camera position before creating scene on it.
|
||||
my_camera = Sketchup::Camera.new(origin, target, [0, 0, 1], !view['isOrthogonal'], view['lens'])
|
||||
sketchup_model.active_view.camera = my_camera
|
||||
sketchup_model.pages.add(view['name'])
|
||||
end
|
||||
end
|
||||
|
||||
# @param headless_layers [Array<String>] headless layer names.
|
||||
# @param folder [Sketchup::Layers, Sketchup::LayerFolder] layer folder to create commit layers under it.
|
||||
def create_headless_layers(headless_layers, folder)
|
||||
headless_layers.each do |layer_name|
|
||||
# Add layer first to the layers object of sketchup model.
|
||||
layer = sketchup_model.layers.add(layer_name)
|
||||
folder.add_layer(layer) unless folder.layers.any? { |l| l.display_name == layer_name }
|
||||
end
|
||||
end
|
||||
|
||||
# Create layers with it's parent folders.
|
||||
# @param folder [Sketchup::LayerFolder] layer folder to create commit layers under it.
|
||||
def create_folder_layers(folder_layer_arrays, folder)
|
||||
folder_layer_arrays.each do |folder_layer_array|
|
||||
create_folder_layer(folder_layer_array, folder)
|
||||
end
|
||||
end
|
||||
|
||||
# Create layers that have parent folder(s)- this method is recursive (self-caller) until all tree is created.
|
||||
def create_folder_layer(folder_array, folder)
|
||||
if folder_array.length > 1
|
||||
# add folder if it is not exist.
|
||||
folder.add_folder(folder_array[0]) unless folder.folders.any? { |f| f.display_name == folder_array[0] }
|
||||
new_folder = folder.folders.find { |f| f.display_name == folder_array[0] }
|
||||
create_folder_layer(folder_array[1..-1], new_folder)
|
||||
else
|
||||
# Add layer first to the layers object of sketchup model.
|
||||
layer = sketchup_model.layers.add(folder_array[0])
|
||||
folder.add_layer(layer) unless folder.layers.any? { |l| l.display_name == layer }
|
||||
end
|
||||
end
|
||||
|
||||
# Traversal method to create Sketchup objects from upcoming base object.
|
||||
# @param obj [Hash, Array] object might be source base object or it's sub objects, because this method is a
|
||||
# self-caller method means that call itself according to conditions inside of it.
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
# rubocop:disable Metrics/PerceivedComplexity
|
||||
def traverse_commit_object(obj, commit_folder, layer, model_preferences)
|
||||
if can_convert_to_native(obj)
|
||||
convert_to_native(obj, layer, model_preferences)
|
||||
def traverse_commit_object(obj, layer, entities)
|
||||
if convertible_to_native?(obj)
|
||||
@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)
|
||||
|
||||
@@ -136,131 +271,122 @@ module SpeckleConnector
|
||||
# puts(">>> Found #{obj['speckle_type']}: #{obj['id']}. Continuing traversal.")
|
||||
props = obj.keys.filter_map { |key| key unless key.start_with?('_') }
|
||||
props.each do |prop|
|
||||
layer_path = prop if prop.start_with?('@') && obj[prop].is_a?(Array)
|
||||
layer = find_layer(layer_path, commit_folder, layer)
|
||||
traverse_commit_object(obj[prop], commit_folder, layer, model_preferences)
|
||||
traverse_commit_object(obj[prop], layer, entities)
|
||||
end
|
||||
else
|
||||
# puts(">>> Found #{obj['speckle_type']}: #{obj['id']} with displayValue.")
|
||||
convert_to_native(obj, layer, model_preferences)
|
||||
@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, model_preferences) }
|
||||
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, model_preferences) }
|
||||
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
|
||||
def speckle_object_to_native(obj)
|
||||
return DISPLAY_VALUE.method(:to_native) unless obj['displayValue'].nil? && obj['@displayValue'].nil?
|
||||
|
||||
SPECKLE_OBJECT_TO_NATIVE[obj['speckle_type']]
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def convert_to_native(obj, layer, model_preferences, entities = sketchup_model.entities)
|
||||
convert = method(:convert_to_native)
|
||||
unless obj['displayValue'].nil?
|
||||
return display_value_to_native_component(obj, layer, entities, model_preferences, &convert)
|
||||
end
|
||||
SPECKLE_OBJECT_TO_NATIVE = {
|
||||
OBJECTS_GEOMETRY_LINE => LINE.method(:to_native),
|
||||
OBJECTS_GEOMETRY_POLYLINE => LINE.method(:to_native),
|
||||
OBJECTS_GEOMETRY_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_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),
|
||||
OBJECTS_GIS_LINEELEMENT => LINE_ELEMENT.method(:to_native),
|
||||
SPECKLE_CORE_MODELS_COLLECTION => COLLECTION.method(:to_native),
|
||||
SPECKLE_CORE_MODELS_COLLECTION_RASTER_LAYER => GIS_LAYER_COLLECTION.method(:to_native),
|
||||
SPECKLE_CORE_MODELS_COLLECTION_VECTOR_LAYER => GIS_LAYER_COLLECTION.method(:to_native)
|
||||
}.freeze
|
||||
|
||||
case obj['speckle_type']
|
||||
when 'Objects.Geometry.Line', 'Objects.Geometry.Polyline' then LINE.to_native(obj, layer, entities)
|
||||
when 'Objects.Other.BlockInstance' then BLOCK_INSTANCE.to_native(sketchup_model, obj, layer, entities,
|
||||
model_preferences, &convert)
|
||||
when 'Objects.Other.BlockDefinition' then BLOCK_DEFINITION.to_native(sketchup_model, obj, layer,
|
||||
obj['name'],
|
||||
obj['always_face_camera'],
|
||||
model_preferences,
|
||||
obj['sketchup_attributes'],
|
||||
obj['applicationId'],
|
||||
&convert)
|
||||
when 'Objects.Geometry.Mesh' then MESH.to_native(sketchup_model, obj, layer, entities, model_preferences)
|
||||
when 'Objects.Geometry.Brep' then MESH.to_native(sketchup_model, obj['displayValue'], layer, entities,
|
||||
model_preferences)
|
||||
# @param state [States::State] state of the speckle application
|
||||
def convert_to_native(state, obj, layer, entities = sketchup_model.entities)
|
||||
# store this method as parameter to re-call it inner callstack
|
||||
convert_to_native = method(:convert_to_native)
|
||||
# Get 'to_native' method to convert upcoming speckle object to native sketchup entity
|
||||
to_native_method = speckle_object_to_native(obj)
|
||||
# Call 'to_native' method by passing this method itself to handle nested 'to_native' conversions.
|
||||
# It returns updated state and converted entities.
|
||||
state, converted_entities = to_native_method.call(state, obj, 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)
|
||||
# Create layers from category of object and place object in it
|
||||
create_layers_from_categories(state, obj, converted_entities)
|
||||
end
|
||||
# Create speckle entities from sketchup entities to achieve continuous traversal.
|
||||
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)
|
||||
nil
|
||||
return state, []
|
||||
end
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
# Creates a component definition and instance from a speckle object with a display value
|
||||
# rubocop:disable Metrics/PerceivedComplexity
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def display_value_to_native_component(obj, layer, entities, model_preferences, &convert)
|
||||
obj_id = obj['applicationId'].to_s.empty? ? obj['id'] : obj['applicationId']
|
||||
# rubocop:disable Metrics/PerceivedComplexity
|
||||
def create_layers_from_categories(state, speckle_object, entities)
|
||||
return state if speckle_object['category'].nil?
|
||||
|
||||
block_definition = obj['@blockDefinition'] || obj['blockDefinition']
|
||||
layer = sketchup_model.layers.find { |l| l.display_name == speckle_object['category'] }
|
||||
unless layer.nil?
|
||||
entities.each { |entity| entity.layer = layer if entity.respond_to?(:layer) } if layer
|
||||
return state
|
||||
end
|
||||
|
||||
definition = BLOCK_DEFINITION.to_native(
|
||||
sketchup_model,
|
||||
obj['displayValue'],
|
||||
layer,
|
||||
"def::#{obj_id}",
|
||||
if block_definition.nil?
|
||||
false
|
||||
else
|
||||
block_definition['always_face_camera'].nil? ? false : block_definition['always_face_camera']
|
||||
end,
|
||||
model_preferences,
|
||||
if block_definition.nil?
|
||||
nil
|
||||
else
|
||||
block_definition['sketchup_attributes'].nil? ? nil : block_definition['sketchup_attributes']
|
||||
end,
|
||||
obj_id,
|
||||
&convert
|
||||
)
|
||||
|
||||
find_and_erase_existing_instance(definition, obj_id)
|
||||
t_arr = obj['transform']
|
||||
transform = t_arr.nil? ? Geom::Transformation.new : OTHER::Transform.to_native(t_arr, units)
|
||||
instance = entities.add_instance(definition, transform)
|
||||
instance.name = obj_id
|
||||
instance
|
||||
layer = sketchup_model.layers.add(speckle_object['category'])
|
||||
unless layer.nil?
|
||||
entities.each { |entity| entity.layer = layer if entity.respond_to?(:layer) } if layer
|
||||
state
|
||||
end
|
||||
state
|
||||
end
|
||||
# rubocop:enable Metrics/PerceivedComplexity
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
# rubocop:enable Metrics/PerceivedComplexity
|
||||
|
||||
# Takes a component definition and finds and erases the first instance with the matching name
|
||||
# (and optionally the applicationId)
|
||||
def find_and_erase_existing_instance(definition, name, app_id = '')
|
||||
definition.instances.find { |ins| ins.name == name || ins.guid == app_id }&.erase!
|
||||
# @param state [States::State] state of the speckle application
|
||||
def create_levels(state, speckle_object)
|
||||
level = speckle_object['level']
|
||||
return state if level.nil?
|
||||
return state unless level['speckle_type'].include?('Objects.BuiltElements.Level')
|
||||
|
||||
level_name = level['name'] || level['id']
|
||||
is_exist = @entities_to_fill.grep(Sketchup::SectionPlane).any? { |sp| sp.name == level_name }
|
||||
return state if is_exist
|
||||
|
||||
elevation = SpeckleObjects::Geometry.length_to_native(level['elevation'], level['units'])
|
||||
|
||||
section_plane = @entities_to_fill.add_section_plane([0, 0, elevation + LEVEL_SHIFT_VALUE], [0, 0, -1])
|
||||
section_plane.name = level_name
|
||||
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.write_initial_base_data(
|
||||
section_plane, level['applicationId'], level['id'], level['speckle_type'], [], @stream_id
|
||||
)
|
||||
state
|
||||
end
|
||||
|
||||
# @param state [States::State] state of the application
|
||||
def convert_to_speckle_entities(state, speckle_objects_with_entities)
|
||||
return state if speckle_objects_with_entities.empty?
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/ClassLength
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,172 +8,114 @@ require_relative '../speckle_objects/geometry/length'
|
||||
require_relative '../speckle_objects/geometry/mesh'
|
||||
require_relative '../speckle_objects/other/block_instance'
|
||||
require_relative '../speckle_objects/other/block_definition'
|
||||
require_relative '../speckle_objects/other/rendering_options'
|
||||
require_relative '../speckle_objects/built_elements/view3d'
|
||||
require_relative '../speckle_objects/built_elements/revit/direct_shape'
|
||||
require_relative '../speckle_objects/relations/layers'
|
||||
require_relative '../speckle_objects/speckle/core/models/model_collection'
|
||||
require_relative '../constants/path_constants'
|
||||
require_relative '../sketchup_model/reader/speckle_entities_reader'
|
||||
require_relative '../sketchup_model/reader/mapper_reader'
|
||||
require_relative '../sketchup_model/query/entity'
|
||||
|
||||
module SpeckleConnector
|
||||
module Converters
|
||||
# Converts sketchup entities to speckle objects.
|
||||
class ToSpeckle < Converter
|
||||
# @return [Hash{Symbol=>Array}] layers to hold it's objects under the base object.
|
||||
attr_reader :layers
|
||||
|
||||
def initialize(sketchup_model)
|
||||
super(sketchup_model)
|
||||
@layers = add_all_layers
|
||||
end
|
||||
MODEL_COLLECTION = SpeckleObjects::Speckle::Core::Models::ModelCollection
|
||||
DIRECT_SHAPE = SpeckleObjects::BuiltElements::Revit::DirectShape
|
||||
SPECKLE_ENTITIES_READER = SketchupModel::Reader::SpeckleEntitiesReader
|
||||
VIEW3D = SpeckleObjects::BuiltElements::View3d
|
||||
|
||||
# Convert selected objects by putting them into related array that grouped by layer.
|
||||
# @return [Hash{Symbol=>Array}] layers -which only have objects- to hold it's objects under the base object.
|
||||
def convert_selection_to_base(preferences)
|
||||
sketchup_model.selection.each do |entity|
|
||||
converted_object = convert(entity, preferences)
|
||||
layer_name = entity_layer_path(entity)
|
||||
layers[layer_name].push(converted_object)
|
||||
end
|
||||
# send only layers that have any object
|
||||
base_object_properties = layers.reject { |_layer_name, objects| objects.empty? }
|
||||
add_views(base_object_properties) if sketchup_model.pages.any?
|
||||
SpeckleObjects::Base.with_detached_layers(base_object_properties)
|
||||
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.
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def add_views(base_object_properties)
|
||||
views = []
|
||||
sketchup_model.pages.each do |page|
|
||||
cam = page.camera
|
||||
origin = SpeckleObjects::Geometry::Point.new(
|
||||
SpeckleObjects::Geometry.length_to_speckle(cam.eye[0], @units),
|
||||
SpeckleObjects::Geometry.length_to_speckle(cam.eye[1], @units),
|
||||
SpeckleObjects::Geometry.length_to_speckle(cam.eye[2], @units),
|
||||
@units
|
||||
)
|
||||
target = SpeckleObjects::Geometry::Point.new(
|
||||
SpeckleObjects::Geometry.length_to_speckle(cam.target[0], @units),
|
||||
SpeckleObjects::Geometry.length_to_speckle(cam.target[1], @units),
|
||||
SpeckleObjects::Geometry.length_to_speckle(cam.target[2], @units),
|
||||
@units
|
||||
)
|
||||
direction = SpeckleObjects::Geometry::Vector.new(
|
||||
SpeckleObjects::Geometry.length_to_speckle(cam.direction[0], @units),
|
||||
SpeckleObjects::Geometry.length_to_speckle(cam.direction[1], @units),
|
||||
SpeckleObjects::Geometry.length_to_speckle(cam.direction[2], @units),
|
||||
@units
|
||||
)
|
||||
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
|
||||
)
|
||||
views.append(view)
|
||||
end
|
||||
base_object_properties['@Named Views'] = views
|
||||
return new_speckle_state, model_collection
|
||||
end
|
||||
# rubocop:enable Metrics/AbcSize
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
# Serialized and traversed information to send batches.
|
||||
# @param base [SpeckleObjects::Base] base object to serialize.
|
||||
# @param base_and_entity [SpeckleObjects::Base] base object to serialize.
|
||||
# @return [String, Integer, Array<Object>] base id, total_children_count of base and batches
|
||||
def send_info(base)
|
||||
serializer = SpeckleConnector::Converters::BaseObjectSerializer.new
|
||||
# t = Time.now.to_f
|
||||
id, _traversed = serializer.serialize(base)
|
||||
# puts "Generating traversed object elapsed #{Time.now.to_f - t} s"
|
||||
def serialize(base_and_entity, speckle_state, preferences)
|
||||
serializer = SpeckleConnector::Converters::BaseObjectSerializer.new(speckle_state, stream_id, preferences)
|
||||
t = Time.now.to_f
|
||||
id = serializer.serialize(base_and_entity)
|
||||
batches = serializer.batch_objects
|
||||
# write_to_speckle_folder(id, batches)
|
||||
puts "Generating traversed object elapsed #{Time.now.to_f - t} s"
|
||||
base_total_children_count = serializer.total_children_count(id)
|
||||
return id, base_total_children_count, serializer.batch_objects
|
||||
return id, base_total_children_count, batches, serializer.speckle_state
|
||||
end
|
||||
|
||||
def write_to_speckle_folder(id, batches)
|
||||
folder_path = "#{HOME_PATH}/Speckle"
|
||||
file_path = "#{folder_path}/#{id}.json"
|
||||
FileUtils.mkdir_p(folder_path) unless File.exist?(folder_path)
|
||||
File.write(file_path, batches.first)
|
||||
end
|
||||
|
||||
# @param entity [Sketchup::Entity] sketchup entity to convert Speckle.
|
||||
def convert(entity, preferences)
|
||||
# @param speckle_state [States::SpeckleState] the current speckle state of the {States::State}
|
||||
# @param parent [Symbol, String] parent of the Sketchup Entity to be converted.
|
||||
def convert(entity, preferences, speckle_state, parent = :base)
|
||||
convert = method(:convert)
|
||||
|
||||
unless SketchupModel::Reader::MapperReader.mapped_with_schema?(entity) &&
|
||||
!entity.is_a?(Sketchup::ComponentDefinition)
|
||||
return from_native_to_speckle(entity, preferences, speckle_state, parent, &convert)
|
||||
end
|
||||
|
||||
return speckle_state, nil
|
||||
end
|
||||
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def from_native_to_speckle(entity, preferences, speckle_state, parent, &convert)
|
||||
if entity.is_a?(Sketchup::Edge)
|
||||
return SpeckleObjects::Geometry::Line.from_edge(entity, @units, preferences[:model]).to_h
|
||||
line = SpeckleObjects::Geometry::Line.from_edge(entity, @units, preferences[:model]).to_h
|
||||
return speckle_state, [line, [entity]]
|
||||
end
|
||||
|
||||
if entity.is_a?(Sketchup::Face)
|
||||
return SpeckleObjects::Geometry::Mesh.from_face(entity, @units, preferences[:model])
|
||||
mesh = SpeckleObjects::Geometry::Mesh.from_face(speckle_state: speckle_state, face: entity, units: @units,
|
||||
model_preferences: preferences[:model])
|
||||
return speckle_state, [mesh, [entity]]
|
||||
end
|
||||
|
||||
if entity.is_a?(Sketchup::Group)
|
||||
return SpeckleObjects::Other::BlockInstance.from_group(entity, @units, @definitions, preferences, &convert)
|
||||
new_speckle_state, block_instance = SpeckleObjects::Other::BlockInstance.from_group(
|
||||
entity, @units, preferences, speckle_state, &convert
|
||||
)
|
||||
speckle_state = new_speckle_state
|
||||
return speckle_state, [block_instance, [entity]]
|
||||
end
|
||||
|
||||
if entity.is_a?(Sketchup::ComponentInstance)
|
||||
return SpeckleObjects::Other::BlockInstance.from_component_instance(entity, @units, @definitions,
|
||||
preferences, &convert)
|
||||
new_speckle_state, block_instance = SpeckleObjects::Other::BlockInstance.from_component_instance(
|
||||
entity, @units, preferences, speckle_state, &convert
|
||||
)
|
||||
speckle_state = new_speckle_state
|
||||
return speckle_state, [block_instance, [entity]]
|
||||
end
|
||||
|
||||
if entity.is_a?(Sketchup::ComponentDefinition)
|
||||
return SpeckleObjects::Other::BlockDefinition.from_definition(entity, @units, @definitions, preferences,
|
||||
&convert)
|
||||
# Local caching
|
||||
return speckle_state, [definitions[entity.guid], [entity]] if definitions.key?(entity.guid)
|
||||
|
||||
new_speckle_state, block_definition = SpeckleObjects::Other::BlockDefinition.from_definition(
|
||||
entity, @units, preferences, speckle_state, parent, &convert
|
||||
)
|
||||
definitions[entity.guid] = block_definition
|
||||
speckle_state = new_speckle_state
|
||||
return speckle_state, [block_definition, [entity]]
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
# Create layers -> {Hash{Symbol=>Array}} from sketchup model with empty array as hash entry values.
|
||||
# This method add first headless layers (not belong to any folder),
|
||||
# then goes through each folder, their sub-folders and their layers.
|
||||
# @return [Hash{Symbol=>Array}] layers from sketchup model with empty array as hash entry values.
|
||||
def add_all_layers
|
||||
# add headless layers
|
||||
layer_objects = add_layers(sketchup_model.layers.layers)
|
||||
# add layers from folders
|
||||
add_layers_from_folders(sketchup_model.layers.folders, layer_objects)
|
||||
layer_objects
|
||||
end
|
||||
|
||||
# @param layers [Array<Sketchup::Layer>] layers in sketchup model
|
||||
# @return [Hash{Symbol=>Array}] layers with empty array value.
|
||||
def add_layers(layers, layer_objects = {}, parent_name = '')
|
||||
layers.each do |layer|
|
||||
layer_name = parent_name.empty? ? "@#{layer.display_name}" : "#{parent_name}::#{layer.display_name}"
|
||||
layer_objects[layer_name] = []
|
||||
end
|
||||
layer_objects
|
||||
end
|
||||
|
||||
# @param folders [Array<Sketchup::LayerFolder>] layer folders in sketchup model.
|
||||
# @param layer_objects [Hash{Symbol=>Array}] layer objects to fill in.
|
||||
# @param parent_name [String] parent folder name to structure layer path before send to Speckle.
|
||||
# ex: "@#{parent_name}::#{layer_name}"
|
||||
def add_layers_from_folders(folders, layer_objects, parent_name = '')
|
||||
folders.each do |folder|
|
||||
folder_name = parent_name.empty? ? "@#{folder.display_name}" : "#{parent_name}::#{folder.display_name}"
|
||||
add_layers(folder.layers, layer_objects, folder_name)
|
||||
add_layers_from_folders(folder.folders, layer_objects, folder_name) unless folder.folders.empty?
|
||||
end
|
||||
end
|
||||
|
||||
# Find layer path of given Sketchup entity.
|
||||
# @param entity [Sketchup::Entity] entity to find root layer.
|
||||
# @return [String] layer path of Sketchup entity.
|
||||
def entity_layer_path(entity)
|
||||
layer_name = entity.layer.display_name
|
||||
if entity.layer.folder.nil?
|
||||
"@#{layer_name}"
|
||||
else
|
||||
folders = folder_name(entity.layer.folder)
|
||||
path = ''
|
||||
folders.reverse.each do |folder|
|
||||
path += "#{folder}::"
|
||||
end
|
||||
"@#{path}#{layer_name}"
|
||||
end
|
||||
end
|
||||
|
||||
# Nested method to retrieve sub-folders until nothing found.
|
||||
# @return [Array<String>] folder names as list from bottom to top. Might need to be reversed if you want to see
|
||||
# from top to bottom.
|
||||
def folder_name(folder, folders = [])
|
||||
if folder.folder.nil?
|
||||
folders.push(folder.display_name)
|
||||
else
|
||||
folder_name(folder.folder, folders.push(folder.display_name))
|
||||
end
|
||||
return speckle_state, nil
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
BIN
Binary file not shown.
@@ -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,142 @@
|
||||
# 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,
|
||||
Railings: 110
|
||||
}.freeze
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
def reverse_dictionary
|
||||
dictionary.collect { |k, v| [v, k] }.to_h
|
||||
end
|
||||
|
||||
def to_a
|
||||
dictionary.collect { |k, v| { key: k, value: v } }.to_a
|
||||
end
|
||||
|
||||
def reverse_to_a
|
||||
dictionary.collect { |k, v| { key: v, value: k } }.to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,79 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SpeckleConnector
|
||||
module Mapper
|
||||
module Category
|
||||
# Revit categories for families.
|
||||
class RevitFamilyCategory < Hash
|
||||
class << self
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def dictionary
|
||||
{
|
||||
AudioVisualDevices: 9,
|
||||
CableTrayFitting: 16,
|
||||
Casework: 19,
|
||||
Columns: 21,
|
||||
CommunicationDevices: 22,
|
||||
ConduitFitting: 23,
|
||||
DataDevices: 30,
|
||||
Doors: 32,
|
||||
DuctAccessory: 33,
|
||||
ElectricalEquipment: 38,
|
||||
ElectricalFixtures: 39,
|
||||
Entourage: 40,
|
||||
FireAlarmDevices: 42,
|
||||
FireProtection: 43,
|
||||
FoodServiceEquipment: 45,
|
||||
Furniture: 46,
|
||||
FurnitureSystems: 47,
|
||||
GenericAnnotation: 48,
|
||||
GenericModel: 49,
|
||||
Hardscape: 51,
|
||||
LightingDevices: 52,
|
||||
LightingFixtures: 53,
|
||||
Lines: 54,
|
||||
Mass: 55,
|
||||
MechanicalEquipment: 56,
|
||||
MedicalEquipment: 57,
|
||||
NurseCallDevices: 58,
|
||||
Parking: 59,
|
||||
PipeAccessory: 68,
|
||||
PipeFitting: 69,
|
||||
Planting: 74,
|
||||
PlumbingFixtures: 76,
|
||||
Roads: 80,
|
||||
SecurityDevices: 82,
|
||||
Signage: 84,
|
||||
Site: 85,
|
||||
SpecialityEquipment: 86,
|
||||
Sprinklers: 87,
|
||||
StructuralFramingSystem: 89,
|
||||
StructuralColumns: 90,
|
||||
StructConnections: 91,
|
||||
StructuralFoundation: 93,
|
||||
StructuralFraming: 94,
|
||||
StructuralStiffener: 97,
|
||||
TemporaryStructure: 100,
|
||||
VerticalCirculation: 103,
|
||||
Windows: 109,
|
||||
Railings: 110
|
||||
}.freeze
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
def reverse_dictionary
|
||||
dictionary.collect { |k, v| [v, k] }.to_h
|
||||
end
|
||||
|
||||
def to_a
|
||||
dictionary.collect { |k, v| { key: k, value: v } }.to_a
|
||||
end
|
||||
|
||||
def reverse_to_a
|
||||
dictionary.collect { |k, v| { key: v, value: k } }.to_a
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,62 @@
|
||||
# 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 '../speckle_objects/other/mapped_block_wrapper'
|
||||
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, model_preferences, global_transformation: nil, path: 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
|
||||
@@ -0,0 +1,33 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_observer'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# App observer.
|
||||
class AppObserver < Sketchup::AppObserver
|
||||
include EventObserver
|
||||
|
||||
# rubocop:disable Naming/MethodName
|
||||
# SketchUp observer method triggered when new empty model is created.
|
||||
#
|
||||
# @param model (Sketchup::Model): The active model object.
|
||||
def onNewModel(model)
|
||||
push_event(:onNewModel, model)
|
||||
end
|
||||
|
||||
# SketchUp observer method triggered when previously saved model is opened.
|
||||
#
|
||||
# @param model (Sketchup::Model) - The active model object.
|
||||
def onOpenModel(model)
|
||||
push_event(:onOpenModel, model)
|
||||
end
|
||||
|
||||
# SketchUp observer method triggered when user exists SketchUp.
|
||||
def onQuit
|
||||
push_event(:onQuit)
|
||||
end
|
||||
# rubocop:enable Naming/MethodName
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,26 +1,30 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_observer'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# Entities observer.
|
||||
class EntitiesObserver < Sketchup::EntitiesObserver
|
||||
attr_accessor :registry
|
||||
|
||||
def initialize
|
||||
super()
|
||||
@registry = Sketchup.active_model.attribute_dictionary('speckle_id_registry', true)
|
||||
end
|
||||
include EventObserver
|
||||
|
||||
# rubocop:disable Naming/MethodName
|
||||
def onEraseEntity(entity)
|
||||
app_id = entity.get_attribute('speckle', 'applicationId')
|
||||
return if app_id.nil?
|
||||
# @param entities (Sketchup::Entities)
|
||||
# @param entity (Sketchup::Entity)
|
||||
def onElementAdded(entities, entity)
|
||||
push_event(:onElementAdded, entities, entity)
|
||||
end
|
||||
|
||||
p(app_id)
|
||||
# @param entities (Sketchup::Entities)
|
||||
# @param entity (Sketchup::Entity)
|
||||
def onElementModified(entities, entity)
|
||||
push_event(:onElementModified, entities, entity)
|
||||
end
|
||||
|
||||
@registry.delete_key(app_id)
|
||||
|
||||
p(@registry)
|
||||
# @param entities (Sketchup::Entities)
|
||||
# @param entity_id (Integer) id of the removed entity.
|
||||
def onElementRemoved(entities, entity_id)
|
||||
push_event(:onElementRemoved, entities, entity_id)
|
||||
end
|
||||
# rubocop:enable Naming/MethodName
|
||||
end
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_hash'
|
||||
require_relative '../actions/on_events_action'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# Event handler class.
|
||||
class EventHandler
|
||||
# @return [SpeckleConnectorApp] an object that contains current state of speckle objects
|
||||
attr_reader :app
|
||||
|
||||
def initialize(app)
|
||||
@app = app
|
||||
end
|
||||
|
||||
def handle_events(events)
|
||||
run_handlers(events)
|
||||
end
|
||||
|
||||
def run_handlers(events)
|
||||
app.update_state!(Actions::OnEventsAction, events)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,20 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../ext/immutable_ruby/core'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# Collection of events.
|
||||
class EventHash < Immutable::Hash
|
||||
def push_event(observer_class, event_name, data)
|
||||
observer_events = self[observer_class] || Immutable::EmptyHash
|
||||
name_events = observer_events[event_name] || Immutable::EmptyVector
|
||||
name_events = name_events.add(data)
|
||||
observer_events = observer_events.put(event_name, name_events)
|
||||
put(observer_class, observer_events)
|
||||
end
|
||||
end
|
||||
|
||||
EMPTY_EVENT_HASH = EventHash.new([])
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,43 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_hash'
|
||||
require_relative '../actions/on_events_action'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# Observer classes includes it to check common operations for all observer classes.
|
||||
module EventObserver
|
||||
# @return [ObserverHandler] handler for observer events
|
||||
attr_reader :observer_handler
|
||||
|
||||
def initialize(observer_handler)
|
||||
super()
|
||||
@observer_handler = observer_handler
|
||||
end
|
||||
|
||||
def push_event(event_name, *event_data)
|
||||
return if observers_disabled?
|
||||
|
||||
@observer_handler.handle_event!(self.class.name, event_name, event_data)
|
||||
end
|
||||
|
||||
def observers_disabled?
|
||||
@observer_handler.observers_disabled?
|
||||
end
|
||||
|
||||
# Push event only once. If the event is already registered, don't push it again
|
||||
# @param event_name [Symbol] the name of the event
|
||||
# @param event_data [Array] the optional data that comes with the event
|
||||
def push_once(event_name, *event_data)
|
||||
return if observers_disabled?
|
||||
|
||||
# Don't push anything if the selection event was already registered
|
||||
class_events = @observer_handler.events[self.class]
|
||||
events = class_events && class_events[event_name]
|
||||
return if events&.any?
|
||||
|
||||
push_event(event_name, *event_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,32 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'app_observer'
|
||||
require_relative 'entities_observer'
|
||||
require_relative 'observer_handler'
|
||||
require_relative 'model_observer'
|
||||
require_relative 'event_handler'
|
||||
require_relative 'selection_observer'
|
||||
require_relative '../constants/observer_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# Factory class to create observers and it's handler
|
||||
module Factory
|
||||
module_function
|
||||
|
||||
def create_handler(app)
|
||||
event_handler = EventHandler.new(app)
|
||||
ObserverHandler.new(event_handler)
|
||||
end
|
||||
|
||||
def create_observers(handler)
|
||||
{
|
||||
APP_OBSERVER => AppObserver.new(handler),
|
||||
ENTITIES_OBSERVER => EntitiesObserver.new(handler),
|
||||
MODEL_OBSERVER => ModelObserver.new(handler),
|
||||
SELECTION_OBSERVER => SelectionObserver.new(handler)
|
||||
}.freeze
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,19 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_observer'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# Model related event observers.
|
||||
class ModelObserver < Sketchup::ModelObserver
|
||||
include EventObserver
|
||||
|
||||
# rubocop:disable Naming/MethodName
|
||||
# @param model (Sketchup::Model)
|
||||
def onActivePathChanged(model)
|
||||
push_event(:onActivePathChanged, model)
|
||||
end
|
||||
# rubocop:enable Naming/MethodName
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,68 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_hash'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# Class to handle observer events.
|
||||
class ObserverHandler
|
||||
# @return [EventHash] registered events
|
||||
attr_reader :events
|
||||
|
||||
# @return [Float] time when first observer was added
|
||||
attr_reader :start_time
|
||||
|
||||
def initialize(event_handler)
|
||||
@event_handler = event_handler
|
||||
clear_events!
|
||||
@observers_disabled = false
|
||||
@observers_in_progress = false
|
||||
@timer_in_progress = false
|
||||
end
|
||||
|
||||
def handle_event!(observer_class, event_name, data)
|
||||
puts "## Register #{observer_class} event #{event_name} ##"
|
||||
puts " data = #{data.inspect}"
|
||||
return if observers_disabled?
|
||||
|
||||
@events = @events.push_event(observer_class, event_name, data)
|
||||
finish
|
||||
end
|
||||
|
||||
def finish
|
||||
return if @observers_in_progress
|
||||
|
||||
@observers_in_progress = true
|
||||
@start_time = Time.now.to_f
|
||||
UI.start_timer(0, false) { finish_in_timer }
|
||||
end
|
||||
|
||||
def finish_in_timer
|
||||
return if @timer_in_progress
|
||||
|
||||
@timer_in_progress = true
|
||||
@event_handler.handle_events(events)
|
||||
ensure
|
||||
clear_events!
|
||||
@observers_in_progress = false
|
||||
@timer_in_progress = false
|
||||
end
|
||||
|
||||
def observers_disabled?
|
||||
@observers_disabled
|
||||
end
|
||||
|
||||
def with_observers_disabled(&block)
|
||||
previous_state = @observers_disabled
|
||||
@observers_disabled = true
|
||||
block.call
|
||||
ensure
|
||||
@observers_disabled = previous_state
|
||||
end
|
||||
|
||||
def clear_events!
|
||||
@events = EMPTY_EVENT_HASH
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,49 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'event_observer'
|
||||
|
||||
module SpeckleConnector
|
||||
module Observers
|
||||
# @see https://ruby.sketchup.com/Sketchup/SelectionObserver.html
|
||||
class SelectionObserver < Sketchup::SelectionObserver
|
||||
include EventObserver
|
||||
|
||||
# rubocop:disable Naming/MethodName
|
||||
# @param _selection (Sketchup::Selection)
|
||||
# @param _entity (Sketchup::Entity)
|
||||
def onSelectionAdded(_selection, _entity)
|
||||
push_selection_event(:onSelectionAdded)
|
||||
end
|
||||
|
||||
# @param _selection (Sketchup::Selection)
|
||||
def onSelectionBulkChange(_selection)
|
||||
push_selection_event(:onSelectionBulkChange)
|
||||
end
|
||||
|
||||
# @param _selection (Sketchup::Selection)
|
||||
def onSelectionCleared(_selection)
|
||||
push_selection_event(:onSelectionCleared)
|
||||
end
|
||||
|
||||
# @param _selection (Sketchup::Selection)
|
||||
def onSelectionRemoved(_selection, _entity)
|
||||
push_selection_event(:onSelectionRemoved)
|
||||
end
|
||||
|
||||
# Due to a SketchUp bug, this method is called by the wrong name.
|
||||
alias onSelectedRemoved onSelectionRemoved
|
||||
# rubocop:enable Naming/MethodName
|
||||
|
||||
private
|
||||
|
||||
# Selection changes need to be registered only once
|
||||
def push_selection_event(event_name)
|
||||
# Don't push anything if the selection event was already registered
|
||||
selection_events = observer_handler.events[self.class]
|
||||
return if selection_events&.any?
|
||||
|
||||
push_event(event_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -3,6 +3,7 @@
|
||||
require_relative '../ext/sqlite3'
|
||||
require_relative '../immutable/immutable'
|
||||
require_relative '../constants/path_constants'
|
||||
require_relative '../constants/pref_constants'
|
||||
require_relative '../sketchup_model/dictionary/speckle_model_dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
@@ -10,66 +11,96 @@ module SpeckleConnector
|
||||
module Preferences
|
||||
include Immutable::ImmutableUtils
|
||||
DICT_HANDLER = SketchupModel::Dictionary::SpeckleModelDictionaryHandler
|
||||
DEFAULT_PREFERENCES = "('configSketchup', '{\"DarkTheme\":false}');"
|
||||
# rubocop:disable Layout/LineLength
|
||||
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, "fe2": false}'
|
||||
|
||||
# @param sketchup_model [Sketchup::Model] active model.
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def self.init_preferences(sketchup_model)
|
||||
# Init sqlite database
|
||||
def self.read_preferences(sketchup_model)
|
||||
db = Sqlite3::Database.new(SPECKLE_CONFIG_DB_PATH)
|
||||
user_preferences = validate_user_preferences(db)
|
||||
model_preferences = validate_model_preferences(sketchup_model)
|
||||
Immutable::Hash.new(
|
||||
{
|
||||
user: user_preferences,
|
||||
model: model_preferences
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
# Check configSketchup key is valid or not, otherwise init with default settings
|
||||
if db.exec("SELECT content FROM 'objects' WHERE hash = 'configSketchup'").empty?
|
||||
db.exec("INSERT INTO 'objects' VALUES #{DEFAULT_PREFERENCES}")
|
||||
# Whether row data is complete with preference or not.
|
||||
# It is useful for backward compatibility, when we add new preferences it should be reset when user initialize it.
|
||||
def self.data_complete?(row_data)
|
||||
return false if row_data.empty?
|
||||
|
||||
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
|
||||
rescue StandardError
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Validates current preferences. If there are incomplete data then this method resets it with default preferences.
|
||||
# @param database [Sqlite3::Database] database for queries.
|
||||
# rubocop:disable Metrics/MethodLength
|
||||
def self.validate_user_preferences(database)
|
||||
row_data = database.exec("SELECT content FROM 'objects' WHERE hash = 'configSketchup'")
|
||||
is_config_sketchup_exist = !row_data.empty?
|
||||
is_data_complete = data_complete?(row_data)
|
||||
if !is_config_sketchup_exist || !is_data_complete
|
||||
if is_config_sketchup_exist
|
||||
unless is_data_complete
|
||||
# Update table with default preferences
|
||||
database.exec("UPDATE 'objects' SET content = '#{DEFAULT_PREFERENCES}' WHERE hash = 'configSketchup'")
|
||||
end
|
||||
else
|
||||
# Insert configSketchup completely to objects.
|
||||
database.exec("INSERT INTO 'objects' VALUES #{DEFAULT_CONFIG}")
|
||||
end
|
||||
end
|
||||
|
||||
# Select data
|
||||
data = db.exec("SELECT content FROM 'objects' WHERE hash = 'configSketchup'").first.first
|
||||
|
||||
row_data = database.exec("SELECT content FROM 'objects' WHERE hash = 'configSketchup'").first.first
|
||||
# Parse string to hash
|
||||
data_hash = JSON.parse(data).to_h
|
||||
|
||||
data_hash = JSON.parse(row_data).to_h
|
||||
# Get current theme value
|
||||
dark_theme = data_hash['DarkTheme']
|
||||
dark_theme = data_hash['dark_theme']
|
||||
fe2 = data_hash['fe2']
|
||||
diffing = data_hash['diffing']
|
||||
register_speckle_entity = data_hash['register_speckle_entity']
|
||||
|
||||
speckle_dictionary = sketchup_model.attribute_dictionary('Speckle')
|
||||
|
||||
if speckle_dictionary
|
||||
Immutable::Hash.new(
|
||||
{
|
||||
user: {
|
||||
dark_theme: dark_theme
|
||||
},
|
||||
model: {
|
||||
combine_faces_by_material: DICT_HANDLER.get_attribute(sketchup_model,
|
||||
:combine_faces_by_material, 'Speckle'),
|
||||
include_entity_attributes: DICT_HANDLER.get_attribute(sketchup_model,
|
||||
:include_entity_attributes, 'Speckle'),
|
||||
merge_coplanar_faces: DICT_HANDLER.get_attribute(sketchup_model,
|
||||
:merge_coplanar_faces, 'Speckle')
|
||||
}
|
||||
}
|
||||
)
|
||||
else
|
||||
DICT_HANDLER.write_initial_model_data(sketchup_model, default_model_preferences)
|
||||
Immutable::Hash.new(
|
||||
{
|
||||
user: {
|
||||
dark_theme: dark_theme
|
||||
},
|
||||
model: default_model_preferences
|
||||
}
|
||||
)
|
||||
end
|
||||
{
|
||||
dark_theme: dark_theme,
|
||||
fe2: fe2,
|
||||
diffing: diffing,
|
||||
register_speckle_entity: register_speckle_entity
|
||||
}.freeze
|
||||
end
|
||||
# rubocop:enable Metrics/MethodLength
|
||||
|
||||
def self.default_model_preferences
|
||||
{
|
||||
combine_faces_by_material: true,
|
||||
include_entity_attributes: true,
|
||||
merge_coplanar_faces: true
|
||||
}
|
||||
# @param sketchup_model [Sketchup::Model] sketchup model to validate model preferences
|
||||
def self.validate_model_preferences(sketchup_model)
|
||||
speckle_dictionary = sketchup_model.attribute_dictionary('Speckle')
|
||||
if speckle_dictionary.nil?
|
||||
DICT_HANDLER.write_initial_model_data(sketchup_model, DEFAULT_MODEL_PREFERENCES)
|
||||
return DEFAULT_MODEL_PREFERENCES
|
||||
end
|
||||
|
||||
DEFAULT_MODEL_PREFERENCES.collect do |pref_key, default_value|
|
||||
pref_value = DICT_HANDLER.get_attribute(
|
||||
sketchup_model,
|
||||
pref_key,
|
||||
'Speckle'
|
||||
)
|
||||
DICT_HANDLER.set_attribute(sketchup_model, pref_key, default_value, 'Speckle') if pref_value.nil?
|
||||
pref_value.nil? ? [pref_key, default_value] : [pref_key, pref_value]
|
||||
end.to_h
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -8,6 +8,8 @@ module SpeckleConnector
|
||||
# `A has child B` is Many to One relationship. Each person can have only one parent,
|
||||
# but one parent can have several children.
|
||||
class ManyToOneRelation
|
||||
attr_reader :parent_table, :children_table
|
||||
|
||||
# @param child [Object] the child element in the relation to look for parent
|
||||
# @return [Object] the parent element that is in relation with the child
|
||||
# @return [nil] if the child element is not in the relation with any elements
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../constants/dict_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
# Operations related to {SketchupModel}.
|
||||
module SketchupModel
|
||||
# Definitions to store for entities.
|
||||
class Definitions
|
||||
def self.from_sketchup_model(model)
|
||||
definitions = model.definitions
|
||||
definitions = definitions.select do |definition|
|
||||
!definition.attribute_dictionaries.nil? &&
|
||||
definition.attribute_dictionaries.any? { |dict| dict.name == SPECKLE_BASE_OBJECT }
|
||||
end
|
||||
Definitions.new(definitions)
|
||||
end
|
||||
|
||||
def add_definition(definition)
|
||||
old_definition = @definitions_by_guid[definition.guid]
|
||||
return self if definition == old_definition
|
||||
|
||||
new_definitions = @definitions.append(definition)
|
||||
Definitions.new(new_definitions)
|
||||
end
|
||||
|
||||
def initialize(definitions = [])
|
||||
@definitions = definitions
|
||||
@definitions_by_name = definitions.collect do |definition|
|
||||
[definition.name, definition]
|
||||
end.to_h.freeze
|
||||
@definitions_by_guid = definitions.collect do |definition|
|
||||
[definition.guid, definition]
|
||||
end.to_h.freeze
|
||||
freeze
|
||||
end
|
||||
|
||||
def by_guid(guid)
|
||||
@definitions_by_guid[guid]
|
||||
end
|
||||
|
||||
def by_name(name)
|
||||
@definitions_by_name[name]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,89 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'delegate'
|
||||
require_relative 'dictionary_handler'
|
||||
require_relative '../../constants/dict_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module SketchupModel
|
||||
module Dictionary
|
||||
# Read and write attributes for Speckle objects on SketchUp model.
|
||||
class BaseDictionaryHandler < DictionaryHandler
|
||||
IGNORED_DICTIONARY_NAMES = [
|
||||
SPECKLE_BASE_OBJECT,
|
||||
'IFC 4',
|
||||
'IFC 2x3'
|
||||
].freeze
|
||||
|
||||
# @param entity [Sketchup::Entity] entity to get attribute dictionaries
|
||||
def self.attribute_dictionaries_to_speckle(entity, model_preferences)
|
||||
dictionaries = {}
|
||||
return dictionaries unless model_preferences[INCLUDE_ENTITY_ATTRIBUTES]
|
||||
|
||||
klass = get_entity_setting_type(entity)
|
||||
return dictionaries unless model_preferences[ENTITY_KEYS_FOR_INCLUDING_ATTRIBUTES[klass]]
|
||||
return dictionaries if entity.attribute_dictionaries.nil?
|
||||
|
||||
entity.attribute_dictionaries.each do |att_dict|
|
||||
dict_name = att_dict == '' ? 'empty_dictionary_name' : att_dict.name
|
||||
dictionaries[dict_name] = att_dict.to_h unless IGNORED_DICTIONARY_NAMES.include?(att_dict.name)
|
||||
end
|
||||
dictionaries
|
||||
end
|
||||
|
||||
# @param entity [Sketchup::Entity] entity to set attribute dictionaries
|
||||
# rubocop:disable Metrics/CyclomaticComplexity
|
||||
def self.attribute_dictionaries_to_native(entity, dictionaries)
|
||||
return if dictionaries.nil?
|
||||
|
||||
classification_to_native(entity, dictionaries) if entity.is_a?(Sketchup::ComponentDefinition)
|
||||
|
||||
dictionaries.each do |dict_name, entries|
|
||||
next unless entries.is_a?(Hash)
|
||||
|
||||
dict_name = dict_name == 'empty_dictionary_name' ? '' : dict_name
|
||||
entries.each do |key, value|
|
||||
set_attribute(entity, key, value, dict_name)
|
||||
rescue StandardError => e
|
||||
puts("Failed to write key: #{key} value: #{value} to dictionary #{dict_name}")
|
||||
puts(e)
|
||||
end
|
||||
end
|
||||
end
|
||||
# rubocop:enable Metrics/CyclomaticComplexity
|
||||
|
||||
# Classification is ComponentDefinition specific, so they can be added only definition by add_classification
|
||||
# method.
|
||||
# @param definition_entity [Sketchup::ComponentDefinition] definition to add callback
|
||||
def self.classification_to_native(definition_entity, dictionaries)
|
||||
applied_schema_types = dictionaries['AppliedSchemaTypes']
|
||||
return if applied_schema_types.nil?
|
||||
|
||||
applied_schema_types.each do |key, value|
|
||||
definition_entity.add_classification(key, value)
|
||||
end
|
||||
end
|
||||
|
||||
# @return [String] the name of the dictionary to read from
|
||||
def self.dictionary_name
|
||||
SPECKLE_BASE_OBJECT
|
||||
end
|
||||
|
||||
# Gets entity type for including entity attributes setting.
|
||||
# @param entity [Sketchup::Entity] entity to find setting entity.
|
||||
# @return [Sketchup::Face, Sketchup::Edge, Sketchup::Group, Sketchup::ComponentInstance]
|
||||
def self.get_entity_setting_type(entity)
|
||||
klass = entity.class
|
||||
if entity.is_a?(Sketchup::ComponentDefinition)
|
||||
klass = if entity.group?
|
||||
Sketchup::Group
|
||||
else
|
||||
Sketchup::ComponentInstance
|
||||
end
|
||||
end
|
||||
klass
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,41 +1,13 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'delegate'
|
||||
require_relative '../../constants/dict_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module SketchupModel
|
||||
module Dictionary
|
||||
# Read and write attributes from the groups and other entities that represents Speckle objects on SketchUp model.
|
||||
class DictionaryHandler
|
||||
DICTIONARY_NAME = 'Speckle_Base_Object'
|
||||
|
||||
# @param entity [Sketchup::Entity] entity to get attribute dictionaries
|
||||
def self.attribute_dictionaries_to_speckle(entity)
|
||||
dictionaries = {}
|
||||
return dictionaries if entity.attribute_dictionaries.nil?
|
||||
|
||||
entity.attribute_dictionaries.each do |att_dict|
|
||||
dict_name = att_dict == '' ? 'empty_dictionary_name' : att_dict.name
|
||||
dictionaries[dict_name] = att_dict.to_h.to_json unless att_dict.name == 'Speckle_Base_Object'
|
||||
end
|
||||
dictionaries
|
||||
end
|
||||
|
||||
# @param entity [Sketchup::Entity] entity to set attribute dictionaries
|
||||
def self.attribute_dictionaries_to_native(entity, dictionaries)
|
||||
return if dictionaries.nil?
|
||||
|
||||
dictionaries.each do |dict_name, entries|
|
||||
dict_name = dict_name == 'empty_dictionary_name' ? '' : dict_name
|
||||
JSON.parse(entries).each do |key, value|
|
||||
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
|
||||
|
||||
# @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
|
||||
@@ -81,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
|
||||
|
||||
+18
-6
@@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'dictionary_handler'
|
||||
require_relative 'base_dictionary_handler'
|
||||
require_relative '../../constants/dict_constants'
|
||||
require_relative '../../constants/type_constants'
|
||||
|
||||
@@ -9,18 +9,30 @@ module SpeckleConnector
|
||||
module Dictionary
|
||||
# Dictionary handler of the speckle entity.
|
||||
class SpeckleEntityDictionaryHandler < DictionaryHandler
|
||||
DICTIONARY_NAME = SPECKLE_BASE_OBJECT
|
||||
|
||||
# Writes initial data while speckle entity is creating first time.
|
||||
# @param sketchup_entity [Sketchup::Entity] Sketchup entity to write data into it's attribute dictionary.
|
||||
def self.write_initial_base_data(sketchup_entity)
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def self.write_initial_base_data(sketchup_entity, application_id, id, speckle_type, children, stream_id)
|
||||
initial_dict_data = {
|
||||
# Add here more if you want to write here initial data
|
||||
SPECKLE_ID => '',
|
||||
SPECKLE_TYPE => BASE_OBJECT,
|
||||
APPLICATION_ID => '',
|
||||
TOTAL_CHILDREN_COUNT => 0
|
||||
SPECKLE_ID => id,
|
||||
APPLICATION_ID => application_id,
|
||||
SPECKLE_TYPE => speckle_type,
|
||||
TOTAL_CHILDREN_COUNT => children.length,
|
||||
CHILDREN => children,
|
||||
VALID_STREAM_IDS => [stream_id],
|
||||
INVALID_STREAM_IDS => []
|
||||
}
|
||||
set_hash(sketchup_entity, initial_dict_data)
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
# @return [String] the name of the dictionary to read from
|
||||
def self.dictionary_name
|
||||
DICTIONARY_NAME
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'dictionary_handler'
|
||||
require_relative 'base_dictionary_handler'
|
||||
require_relative '../../constants/dict_constants'
|
||||
require_relative '../../constants/type_constants'
|
||||
|
||||
@@ -8,7 +8,7 @@ module SpeckleConnector
|
||||
module SketchupModel
|
||||
module Dictionary
|
||||
# Dictionary handler of the speckle model.
|
||||
class SpeckleModelDictionaryHandler < DictionaryHandler
|
||||
class SpeckleModelDictionaryHandler < BaseDictionaryHandler
|
||||
DICTIONARY_NAME = 'Speckle'
|
||||
# Writes initial data while speckle entity is creating first time.
|
||||
# @param sketchup_model [Sketchup::Model] Sketchup model to write data into it's attribute dictionary.
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require 'delegate'
|
||||
require_relative 'dictionary_handler'
|
||||
require_relative '../../constants/dict_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module SketchupModel
|
||||
module Dictionary
|
||||
# Read and write attributes for Speckle objects' schema on SketchUp model.
|
||||
class SpeckleSchemaDictionaryHandler < DictionaryHandler
|
||||
def self.speckle_schema_to_speckle(entity)
|
||||
schema = {}
|
||||
return schema if entity.attribute_dictionaries.nil?
|
||||
|
||||
schema_dict = entity.attribute_dictionaries.find { |dict| dict.name == dictionary_name }
|
||||
return schema if schema_dict.nil?
|
||||
|
||||
schema_dict.to_h
|
||||
end
|
||||
|
||||
# @return [String] the name of the dictionary to read from
|
||||
def self.dictionary_name
|
||||
SPECKLE_MAPPING_TOOL_SCHEMA
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,55 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../constants/mat_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
# Operations related to {SketchupModel}.
|
||||
module SketchupModel
|
||||
# Materials to store for entities.
|
||||
class Materials
|
||||
def self.from_sketchup_model(model)
|
||||
materials = model.materials
|
||||
materials = materials.select do |material|
|
||||
!material.attribute_dictionaries.nil? &&
|
||||
material.attribute_dictionaries.any? { |dict| dict.name == MAT_DICTIONARY }
|
||||
end
|
||||
mat_hash = materials.collect { |material| [material.get_attribute(MAT_DICTIONARY, MAT_ID), material] }.to_h
|
||||
Materials.new(mat_hash)
|
||||
end
|
||||
|
||||
def initialize(material_hash = {})
|
||||
@materials_by_id = material_hash.freeze
|
||||
@id_by_materials = material_hash.collect { |id, material| [material, id] }.to_h.freeze
|
||||
freeze
|
||||
end
|
||||
|
||||
def add_material(id, material)
|
||||
old_material = @materials_by_id[id.to_sym]
|
||||
return self if material == old_material
|
||||
|
||||
new_material_hash = @materials_by_id.merge({ id.to_sym => material })
|
||||
Materials.new(new_material_hash)
|
||||
end
|
||||
|
||||
def by_id(id)
|
||||
@materials_by_id[id.to_sym]
|
||||
end
|
||||
|
||||
def material_id(material)
|
||||
@id_by_materials[material]
|
||||
end
|
||||
|
||||
def add_speckle_material
|
||||
@materials_by_id[MAT_ADD]
|
||||
end
|
||||
|
||||
def edit_speckle_material
|
||||
@materials_by_id[MAT_EDIT]
|
||||
end
|
||||
|
||||
def remove_speckle_material
|
||||
@materials_by_id[MAT_REMOVE]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,113 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module SpeckleConnector
|
||||
module SketchupModel
|
||||
# Query operations in sketchup model.
|
||||
module Query
|
||||
# Queries for entity.
|
||||
class Entity
|
||||
class << self
|
||||
# Creates flat list for entities that defined in classes property. It searches from top to bottom to collect
|
||||
# entities.
|
||||
# @param entities_to_flat [Sketchup::Entities] entities to flat their children, grandchildren and so on..
|
||||
# @param classes [Array<Class>] objects types to collect as flat list.
|
||||
def flat_entities(entities_to_flat,
|
||||
classes = [Sketchup::Edge, Sketchup::Face, Sketchup::ComponentInstance,
|
||||
Sketchup::Group, Sketchup::ComponentDefinition])
|
||||
entities = []
|
||||
entities_to_flat.each do |entity|
|
||||
entities.append(entity) if classes.include?(entity.class)
|
||||
if entity.is_a?(Sketchup::Group) || entity.is_a?(Sketchup::ComponentInstance)
|
||||
entities.append(entity.definition) if classes.include?(Sketchup::ComponentDefinition)
|
||||
entities += flat_entities(entity.definition.entities, classes)
|
||||
end
|
||||
end
|
||||
entities
|
||||
end
|
||||
|
||||
# Create array for each entity with their path.
|
||||
# @param entities_to_flat [Sketchup::Entities, Array<Sketchup::Entity>] entities to flat with their path.
|
||||
# @param classes [Array<Class>] classes to flat. Put class into this array if you want to find their paths.
|
||||
# @param path [Array<Object>] path for entity that we are in.
|
||||
# @return [Array<Object>] entity with it's path as flat array. See example.
|
||||
# @example
|
||||
# path[0] is entity itself
|
||||
# path[1..-1] rest as path from top to bottom
|
||||
def flat_entities_with_path(entities_to_flat,
|
||||
classes = [Sketchup::Edge, Sketchup::Face, Sketchup::ComponentInstance,
|
||||
Sketchup::Group, Sketchup::ComponentDefinition],
|
||||
path = [])
|
||||
entities = []
|
||||
entities_to_flat.each do |entity|
|
||||
# Collect object itself
|
||||
entities.append([entity] + path) if classes.include?(entity.class)
|
||||
# entities[entity] = path if classes.include?(entity.class) && entities[entity].nil?
|
||||
|
||||
# Skip unless entity is a container entity like group or component.
|
||||
next unless entity.is_a?(Sketchup::Group) || entity.is_a?(Sketchup::ComponentInstance)
|
||||
|
||||
# Add entity definition also with it's path.
|
||||
entities[entity.definition] = path if classes.include?(Sketchup::ComponentDefinition)
|
||||
# Collect sub-objects if object is a container at the same time.
|
||||
sub_entities = flat_entities_with_path(entity.definition.entities.to_a,
|
||||
classes, path + [entity])
|
||||
entities += sub_entities
|
||||
end
|
||||
entities
|
||||
end
|
||||
|
||||
# Calculates global transformation of entity by multiplying path entries from bottom to top by reversing path.
|
||||
# @param entity [Sketchup::Entity] entity to find global transformation.
|
||||
# @param path [Array<Object>] path that parents of entity that has transformation value to calculate global
|
||||
# transformation of the entity.
|
||||
# @return [Geom::Transformation] global transformation of the entity.
|
||||
def global_transformation(entity, path)
|
||||
# If entity is face, use Identity
|
||||
global = entity.respond_to?(:transformation) ? entity.transformation : Geom::Transformation.new
|
||||
path.reverse.each do |local|
|
||||
global = local.transformation * global if local.respond_to?(:transformation)
|
||||
end
|
||||
global
|
||||
end
|
||||
|
||||
# Global transformation search for entity that lies on only one instance.
|
||||
# @param entity [Sketchup::Entity] entity to find global transformation.
|
||||
def global_transformation_from_bottom(entity)
|
||||
# If entity is face, use Identity
|
||||
transformation = entity.respond_to?(:transformation) ? entity.transformation : Geom::Transformation.new
|
||||
parent = parent_or_model(entity)
|
||||
until parent.is_a?(Sketchup::Model) || parent.nil?
|
||||
transformation = parent.transformation * transformation
|
||||
parent = parent_or_model(parent)
|
||||
end
|
||||
transformation
|
||||
end
|
||||
|
||||
# Parent search for entity from bottom to top. It is not ideal if entity lives in different instances.
|
||||
def parent_or_model(entity)
|
||||
parent = entity.parent
|
||||
return parent if parent.is_a?(Sketchup::Model)
|
||||
|
||||
instances = parent.instances
|
||||
if instances.length > 1
|
||||
puts 'Parent has more than one instance'
|
||||
instances.each(&:make_unique)
|
||||
instances = instances.select { |instance| instance.definition.entities.include?(entity) }
|
||||
end
|
||||
instances.first
|
||||
end
|
||||
|
||||
# Finds first material of parents from bottom to top.
|
||||
def parent_material(path)
|
||||
material = nil
|
||||
path.reverse.each do |local|
|
||||
material = local.material if local.respond_to?(:material)
|
||||
return material unless material.nil?
|
||||
end
|
||||
material
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,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
|
||||
@@ -0,0 +1,57 @@
|
||||
# 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 speckle entities.
|
||||
module SpeckleEntitiesReader
|
||||
# @param entities [Sketchup::Entities] entities to collect speckle entities.
|
||||
# @return [Hash{String=>Sketchup::Entity}] speckle entities with persistent id.
|
||||
def self.read(entities)
|
||||
speckle_entities = {}
|
||||
entities.each do |entity|
|
||||
speckle_entities[entity.persistent_id] = read_speckle_entity(entity) if speckle_entity?(entity)
|
||||
next unless entity.is_a?(Sketchup::Group) || entity.is_a?(Sketchup::ComponentInstance)
|
||||
|
||||
if speckle_entity?(entity.definition)
|
||||
speckle_entities[entity.definition.persistent_id] = read_speckle_entity(entity.definition)
|
||||
end
|
||||
definition_speckle_entities = read(entity.definition.entities)
|
||||
speckle_entities = speckle_entities.merge(definition_speckle_entities)
|
||||
end
|
||||
speckle_entities
|
||||
end
|
||||
|
||||
# @param entity [Sketchup::Entity] sketchup entity to read from attribute dictionary.
|
||||
def self.read_speckle_entity(entity)
|
||||
dict = entity.attribute_dictionaries.to_a.find { |d| d.name == SPECKLE_BASE_OBJECT }
|
||||
speckle_id = dict[:speckle_id]
|
||||
application_id = dict[:application_id]
|
||||
speckle_type = dict[:speckle_type]
|
||||
children = dict[:children]
|
||||
valid_stream_ids = dict[:valid_stream_ids]
|
||||
invalid_stream_ids = dict[:invalid_stream_ids]
|
||||
SpeckleEntities::SpeckleEntity.new(entity, speckle_id, application_id, speckle_type, children,
|
||||
valid_stream_ids, invalid_stream_ids)
|
||||
end
|
||||
|
||||
# @param entity [Sketchup::Entity] sketchup entity to check if it was speckle entity once.
|
||||
def self.speckle_entity?(entity)
|
||||
return false if entity.attribute_dictionaries.nil?
|
||||
return false if entity.attribute_dictionaries.to_a.empty?
|
||||
|
||||
entity.attribute_dictionaries.to_a.any? { |dict| dict.name == SPECKLE_BASE_OBJECT }
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,39 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../constants/geo_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
# Operations related to {SketchupModel}.
|
||||
module SketchupModel
|
||||
# Works directly with/on SketchUp Entities of different kinds (Groups, Faces, Edges, ...).
|
||||
module Utils
|
||||
# Static methods that work directly with Sketchup::Face objects
|
||||
class FaceUtils
|
||||
# A method that calculates the faces that are less than n-faces
|
||||
# away from the current face. For n=1, you would get the
|
||||
# neighbouring faces.
|
||||
# @param edge_ary [Array<Sketchup::Edge>] the edges to look for n-adjacent faces
|
||||
# @param n_adjacent [Integer] the distance from the edges (in number of faces)
|
||||
# @return [Array<Sketchup::Face>] the faces that are n faces away from the given edges
|
||||
def self.near_faces(edges_ary, n_adjacent = 1)
|
||||
# get all the faces that are not more than a face away to the current face.
|
||||
edges_ary = edges_ary.select(&:valid?)
|
||||
adj_faces = []
|
||||
n_adjacent.times do |_i|
|
||||
new_faces = edges_ary.collect(&:faces).flatten.uniq
|
||||
adj_faces += new_faces
|
||||
edges_ary = new_faces.collect(&:edges).flatten.uniq - edges_ary
|
||||
end
|
||||
adj_faces.uniq
|
||||
end
|
||||
|
||||
# @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
|
||||
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
|
||||
@@ -1,38 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../immutable/immutable'
|
||||
require_relative '../convertors/units'
|
||||
require_relative '../speckle_objects/base'
|
||||
require_relative '../speckle_entities/speckle_line_entity'
|
||||
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleEntities
|
||||
# Speckle base entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
|
||||
class SpeckleBaseEntity
|
||||
include Immutable::ImmutableUtils
|
||||
# @return [Sketchup::Entity] sketchup entity represents {SpeckleEntity} on the model.
|
||||
attr_reader :sketchup_entity
|
||||
|
||||
# @return [SpeckleObjects::Base] speckle object that represented on server.
|
||||
attr_reader :speckle_object
|
||||
|
||||
# @return [Integer] application id of the sketchup entity.
|
||||
attr_reader :application_id
|
||||
|
||||
# @param sketchup_entity [Sketchup::Entity] sketchup entity represents {SpeckleEntity} on the model.
|
||||
def initialize(sketchup_model, sketchup_entity)
|
||||
@sketchup_entity = sketchup_entity
|
||||
@application_id = @sketchup_entity.persistent_id
|
||||
@speckle_object = SpeckleObjects::Base.new
|
||||
su_unit = sketchup_model.options['UnitsOptions']['LengthUnit']
|
||||
@units = Converters::SKETCHUP_UNITS[su_unit]
|
||||
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler.write_initial_base_data(@sketchup_entity)
|
||||
end
|
||||
|
||||
def valid?
|
||||
sketchup_entity.valid?
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,20 +1,159 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../speckle_entities/speckle_line_entity'
|
||||
require_relative 'speckle_entity_status'
|
||||
require_relative '../immutable/immutable'
|
||||
require_relative '../convertors/units'
|
||||
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleEntities
|
||||
# Speckle entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
|
||||
module SpeckleEntity
|
||||
def self.with_converted(skp_model, skp_entity)
|
||||
# return the same object if it is already SpeckleEntity
|
||||
return skp_entity if skp_entity.is_a?(SpeckleEntity)
|
||||
return SpeckleBlockEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::Group)
|
||||
return SpeckleBlockEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::ComponentInstance)
|
||||
return SpeckleMeshEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::Face)
|
||||
# Speckle base entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
|
||||
class SpeckleEntity
|
||||
include Immutable::ImmutableUtils
|
||||
# @return [Sketchup::Entity] Sketchup Entity represents {SpeckleEntity} on the model.
|
||||
attr_reader :sketchup_entity
|
||||
|
||||
SpeckleLineEntity.new(skp_model, skp_entity) if skp_entity.is_a?(Sketchup::Edge)
|
||||
# @return [String] Speckle object type.
|
||||
attr_reader :speckle_type
|
||||
|
||||
# @return [Integer] application id of the Sketchup Entity.
|
||||
attr_reader :application_id
|
||||
|
||||
# @return [String] id of the Speckle Base Object
|
||||
attr_reader :id
|
||||
|
||||
# @return [Integer] total children count of the Speckle Base Object
|
||||
attr_reader :total_children_count
|
||||
|
||||
# @return [Hash{String=>SpeckleObjects::Base}] Speckle objects belongs to edge
|
||||
attr_reader :speckle_children_objects
|
||||
|
||||
# @return [Array<String>] speckle entity that valid on streams
|
||||
attr_reader :valid_stream_ids
|
||||
|
||||
# @return [Array<String>] speckle entity that invalid on streams
|
||||
attr_reader :invalid_stream_ids
|
||||
|
||||
# @return [SpeckleEntityStatus] current status of the Speckle Entity.
|
||||
attr_reader :status
|
||||
|
||||
attr_reader :source_material, :active_diffing_stream_id
|
||||
|
||||
# @param sketchup_entity [Sketchup::Entity] sketchup entity represents {SpeckleEntity} on the model.
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def initialize(sketchup_entity, speckle_id, application_id, speckle_type, children, valid_stream_ids,
|
||||
invalid_stream_ids = [])
|
||||
@status = SpeckleEntityStatus::UP_TO_DATE
|
||||
@source_material = sketchup_entity.material
|
||||
@active_diffing_stream_id = nil
|
||||
@valid_stream_ids = valid_stream_ids
|
||||
@invalid_stream_ids = invalid_stream_ids
|
||||
@sketchup_entity = sketchup_entity
|
||||
@application_id = application_id
|
||||
@id = speckle_id
|
||||
@total_children_count = children.nil? ? 0 : children.length
|
||||
@speckle_type = speckle_type
|
||||
@speckle_children_objects = children.nil? ? [] : children
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
|
||||
def write_initial_base_data
|
||||
SketchupModel::Dictionary::SpeckleEntityDictionaryHandler
|
||||
.write_initial_base_data(@sketchup_entity, application_id, id, speckle_type,
|
||||
@speckle_children_objects, valid_stream_ids.first)
|
||||
end
|
||||
|
||||
def with_up_to_date
|
||||
with(:@status => SpeckleEntityStatus::UP_TO_DATE)
|
||||
end
|
||||
|
||||
def with_invalid
|
||||
valid = valid_stream_ids
|
||||
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, VALID_STREAM_IDS, [])
|
||||
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, INVALID_STREAM_IDS, valid)
|
||||
with(:@valid_stream_ids => [], :@invalid_stream_ids => valid)
|
||||
end
|
||||
|
||||
def activate_diffing(stream_id, material)
|
||||
sketchup_entity.material = material
|
||||
with(:@active_diffing_stream_id => stream_id)
|
||||
end
|
||||
|
||||
def deactivate_diffing
|
||||
sketchup_entity.material = @source_material
|
||||
with(:@active_diffing_stream_id => nil)
|
||||
end
|
||||
|
||||
def change_material(material)
|
||||
sketchup_entity.material = material
|
||||
end
|
||||
|
||||
def revert_material
|
||||
sketchup_entity.material = source_material
|
||||
end
|
||||
|
||||
def with_valid_stream_id(stream_id)
|
||||
return self if valid_stream_ids.include?(stream_id)
|
||||
|
||||
invalid_ids = @invalid_stream_ids.include?(stream_id) ? @invalid_stream_ids - [stream_id] : @invalid_stream_ids
|
||||
valid_ids = valid_stream_ids + [stream_id]
|
||||
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, VALID_STREAM_IDS, valid_ids)
|
||||
sketchup_entity.set_attribute(SPECKLE_BASE_OBJECT, INVALID_STREAM_IDS, invalid_ids)
|
||||
|
||||
# if sketchup_entity.is_a?(Sketchup::Group) || sketchup_entity.is_a?(Sketchup::ComponentInstance)
|
||||
# sketchup_entity.definition.set_attribute(SPECKLE_BASE_OBJECT, VALID_STREAM_IDS, valid_ids)
|
||||
# sketchup_entity.definition.set_attribute(SPECKLE_BASE_OBJECT, INVALID_STREAM_IDS, invalid_ids)
|
||||
# end
|
||||
|
||||
with(:@valid_stream_ids => @valid_stream_ids + [stream_id], :@invalid_stream_ids => invalid_ids)
|
||||
end
|
||||
|
||||
def with_edited
|
||||
with(:@status => SpeckleEntityStatus::EDITED)
|
||||
end
|
||||
|
||||
def with_removed
|
||||
with(:@status => SpeckleEntityStatus::REMOVED)
|
||||
end
|
||||
|
||||
def valid?
|
||||
sketchup_entity.valid?
|
||||
end
|
||||
|
||||
# @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,18 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../immutable/immutable'
|
||||
require_relative '../convertors/units'
|
||||
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleEntities
|
||||
module SpeckleEntityStatus
|
||||
# Speckle Entity created first time with {Sketchup::Entity} before sent to server.
|
||||
UP_TO_DATE = 0
|
||||
# {Sketchup::Entity} that corresponds to Speckle Entity is edited after created by user.
|
||||
EDITED = 1
|
||||
# {Sketchup::Entity} that corresponds to Speckle Entity is removed.
|
||||
REMOVED = 2
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,25 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative 'speckle_base_entity'
|
||||
require_relative '../immutable/immutable'
|
||||
require_relative '../speckle_objects/geometry/line'
|
||||
require_relative '../sketchup_model/dictionary/speckle_entity_dictionary_handler'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleEntities
|
||||
# Speckle line entity is the state object for Sketchup::Entity and it's converted (or not yet) state.
|
||||
class SpeckleLineEntity < SpeckleBaseEntity
|
||||
include Immutable::ImmutableUtils
|
||||
|
||||
# @return [SpeckleObjects::Geometry::Line] speckle line object
|
||||
attr_reader :speckle_object
|
||||
|
||||
def initialize(sketchup_model, sketchup_edge)
|
||||
super(sketchup_model, sketchup_edge)
|
||||
@speckle_object = SpeckleObjects::Geometry::Line.from_edge(sketchup_edge, units)
|
||||
end
|
||||
|
||||
alias sketchup_edge sketchup_entity
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,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, model_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, model_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, model_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, model_preferences, 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: model_preferences,
|
||||
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,37 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
require_relative '../../base'
|
||||
require_relative '../../../constants/type_constants'
|
||||
|
||||
module SpeckleConnector
|
||||
module SpeckleObjects
|
||||
module BuiltElements
|
||||
module Revit
|
||||
# Family instance for Revit mappings.
|
||||
class FamilyInstance < Base
|
||||
SPECKLE_TYPE = OBJECTS_BUILTELEMENTS_REVIT_FAMILY_INSTANCE
|
||||
READER = SketchupModel::Reader
|
||||
QUERY = SketchupModel::Query
|
||||
DICTIONARY = SketchupModel::Dictionary
|
||||
|
||||
# rubocop:disable Metrics/ParameterLists
|
||||
def initialize(family:, type:, level:, units:, base_point:, rotation:, 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[:units] = units
|
||||
self[:basePoint] = base_point
|
||||
self[:rotation] = rotation
|
||||
end
|
||||
# rubocop:enable Metrics/ParameterLists
|
||||
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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user